Logic Controllers in JMeter: Deciding What Runs, When and How Often

Logic Controllers in JMeter: Deciding What Runs, When and How Often

A deep, practical guide to every major logic controller in JMeter: how they shape the order and frequency of requests, how they nest and scope other elements, and how to combine them into realistic, maintainable workload models.

Picture a restaurant kitchen during a busy evening. The chefs know how to cook every dish, but somebody must decide which order tickets are cooked first, which dishes are repeated for large tables, which are skipped because the customer is allergic, and which are prepared only once at the start of the shift, such as the stock for the soup. That coordinator is the head chef, and without one the kitchen produces plenty of food but no coherent service. In JMeter, the samplers are the cooks and the logic controllers are the head chef.

Beginners often build a test plan as a flat list of requests and wonder why the results look nothing like real traffic. Real users log in once, browse many times, buy occasionally, retry when something fails and wander through the site in different orders. Logic controllers are the elements that turn a flat list into that kind of behaviour. This guide starts from the first principle and moves step by step into advanced territory: every major controller, how each one decides what to run, how scoping works, how controllers combine, and the mistakes that quietly corrupt test results.

1What a Logic Controller Actually Is

A logic controller is a container that controls the flow of the requests placed inside it.

A JMeter test plan is a tree. At the top sits the plan itself, below it sit thread groups, and below those sit the elements that do the real work. The elements that actually talk to a server are called samplers, for example an HTTP request or a database query. A logic controller does not talk to any server. Its only job is to decide which of its children run, in what order, how many times and under which condition.

Think of the controller as the rule written on the outside of a box, and the samplers as the items inside the box. One box says “run everything inside three times”. Another says “run only one item from the box, chosen at random”. A third says “run the contents only when the shopping basket is not empty”. By nesting boxes inside boxes you can describe very rich behaviour without writing a single line of code.

Simple Analogy

A logic controller is like the instructions on a board game card. The pieces on the board are the samplers, but the card says “move twice”, “skip this turn” or “draw one at random”. The pieces never decide anything alone; the cards decide the shape of the game.

Why it matters

The credibility of a load test depends on how closely the generated traffic resembles real traffic. Real traffic has proportions, such as seventy percent browsing and five percent purchasing. It has loops, such as paging through search results. It has conditions, such as showing a payment page only for a logged-in user. All of this lives in controllers, so a test plan without them can only ever model the simplest possible visitor.

i
Remember

Controllers decide flow, samplers perform actions, and neither can replace the other. A controller with no samplers inside it does nothing useful, and a pile of samplers with no controllers runs in plain top-to-bottom order.

2The Test Plan Tree and the Order of Execution

To use controllers well, first learn how JMeter walks the tree.

Each virtual user is a thread, and each thread walks the tree under its thread group from top to bottom. When the walker reaches a sampler, the request is sent and the walker moves on. When it reaches a logic controller, the controller takes charge of its own children and tells the walker which one to visit next. When the controller has nothing left to offer for this pass, control returns to the parent. The thread group itself behaves like the top-level controller, which is why a plain list of samplers directly under it runs in order.

flowchart TD
    TP["Test Plan"] --> TG["Thread Group"]
    TG --> LC1["Logic Controller A"]
    TG --> S3["Sampler 3"]
    LC1 --> S1["Sampler 1"]
    LC1 --> LC2["Logic Controller B"]
    LC2 --> S2["Sampler 2"]
    LC2 --> S4["Sampler 4"]
        
FIG 1 — A test plan is a tree and controllers govern their own branch

The idea of an iteration

Every controller keeps its own sense of an iteration. One iteration of a thread group is one full pass of the user’s journey. One iteration of an inner controller is one pass over that controller’s children. This subtle point explains much of the confusion around controllers such as Interleave, Once Only and Throughput, because each one counts passes in terms of its parent’s iterations, not in terms of the whole test.

Where other elements attach

Config elements, timers, assertions, pre-processors, post-processors and listeners are all attached to the tree as well. Their reach is decided by their position, a rule known as scope. An element placed directly under a controller applies to everything in that controller’s branch. An element placed at thread group level applies to the whole group. Understanding scope is essential before arranging controllers, because moving a controller can silently change which timers and assertions apply to a request.

!
Common Mistake

Assuming that a controller only affects the order of requests. It also changes the scope for every timer, assertion and processor beneath it, so moving a request into a controller can add or remove a pause or a check without any visible warning.

3A Map of the Logic Controller Family

Sixteen elements, each answering a different flow question.

JMeter ships with a rich family of controllers. They look overwhelming as a list but become easy once grouped by the question each answers. Some decide how many times, some decide which one, some decide whether at all, some organise for reuse and some measure. The cards below give one-line definitions that the rest of this guide expands.

Organise

Simple Controller

A plain folder that groups children without changing their flow.

Repeat

Loop Controller

Runs its children a set number of times, or forever.

Once

Once Only Controller

Runs its children only on the first pass of each thread, ideal for login.

Rotate

Interleave Controller

Runs one child per pass, rotating through them in order.

Random

Random Controller

Runs one randomly chosen child per pass.

Shuffle

Random Order Controller

Runs all children each pass, but in a random order.

Proportion

Throughput Controller

Runs its children for a chosen percentage or total count of passes.

Time

Runtime Controller

Keeps running its children until a number of seconds has passed.

Condition

If Controller

Runs its children only when a condition is true.

Condition

While Controller

Keeps repeating its children while a condition stays true.

Select

Switch Controller

Runs the one child chosen by a value or name.

Iterate

ForEach Controller

Repeats its children once for every item in a numbered set of variables.

Measure

Transaction Controller

Groups requests and reports them as one business transaction.

Reuse

Module Controller

Runs a reusable block defined elsewhere in the same plan.

Reuse

Include Controller

Pulls in the contents of an external test file.

Protect

Critical Section Controller

Lets only one thread at a time run its children.

The Recording Controller is a seventeenth cousin, which acts as a placeholder where the recorder stores captured requests. The Test Fragment is not a controller at all but a partner of the reuse controllers and is explained in its own chapter.

Question AskedControllers That Answer It
How many times?Loop, Runtime, While, ForEach
Which one?Interleave, Random, Switch
In what order?Simple, Random Order
Whether at all?If, Once Only, Throughput
How to reuse?Module, Include, Test Fragment
How to measure or protect?Transaction, Critical Section

4Simple and Recording Controllers: The Organisers

The quietest controllers are often the most valuable for readability.

The Simple Controller does nothing to the flow. Its children run in order, exactly as they would without it. Its value lies purely in organisation: it lets you group related requests under a meaningful name, collapse the group in the tree, and give a set of elements a shared scope. A shopping journey with login, browse, add to basket and checkout becomes four tidy folders instead of thirty loose rows.

Simple Analogy

A Simple Controller is a labelled drawer in a filing cabinet. The papers inside are not changed by the drawer, but you can find them in seconds and you can say “everything in this drawer gets the same stamp”.

Scoping through a Simple Controller

Because scope follows the tree, a Simple Controller is the neat way to apply a timer, a header manager or an assertion to some requests but not others. Place the element as a child of the controller and its effect stops at the edge of that folder. This is far cleaner than scattering the same timer across many individual samplers.

The Recording Controller

The Recording Controller behaves like a Simple Controller in execution. It exists as the default landing place for requests captured by the HTTP test script recorder, which is why recorded scripts arrive already grouped. Many teams later convert or reorganise these recorded groups into more meaningful folders and transactions.

i
Good Habit

Name every controller after the business step it represents, such as “Login” or “Search products”, not after its type. When a report or a colleague reads the tree months later, the names become documentation.

5The Loop Controller and the Thread Group Loop Count

Two kinds of looping exist, and mixing them up multiplies work unexpectedly.

The Loop Controller repeats all of its children a chosen number of times within a single pass of the user’s journey. The thread group also has its own loop count, which repeats the entire journey. The difference is the level at which repetition happens. The thread group loop is the user coming back to start again, while the Loop Controller is the user repeating one part, such as paging through ten pages of search results.

The two multiply. If the thread group repeats the journey five times and an inner Loop Controller repeats the search step ten times, each user performs the search fifty times in total. Test designers who forget this multiplication end up with ten times the intended load on one endpoint and then blame the server for the slowdown.

flowchart TD
    A["Thread Group iteration begins"] --> B["Login step"]
    B --> C["Loop Controller: repeat paging step"]
    C --> D["Open next results page"]
    D --> E{"More loops left?"}
    E -- "Yes" --> D
    E -- "No" --> F["Checkout step"]
    F --> G["Thread Group iteration ends"]
        
FIG 2 — A Loop Controller repeats a slice of the journey inside one iteration

Fixed counts, variables and infinity

The loop count can be a plain number, a variable that is set elsewhere in the plan, or infinite. A variable is useful when the number of repeats depends on data, for example the number of items found in a response. Infinite looping is rarely used alone, because it never lets the thread leave; it is normally paired with a test duration or with another element that stops the thread.

Paging through results

A search journey that loads the first five pages of results uses a Loop Controller with a count of five, so that the same request group runs five times per visit without five copies of the same elements.

!
Common Mistake

Setting both an infinite thread group loop and an infinite Loop Controller. The inner loop never finishes, so the outer loop never advances, and everything after the inner loop silently stops running.

6The Once Only Controller

Some steps belong at the start of a visit and should never be repeated.

The Once Only Controller runs its children during the first iteration and skips them in every iteration after that. The classic use is login. A real person logs in once and then performs many actions, so repeating the login on every loop would exaggerate the load on the authentication service and leave a very unnatural traffic pattern.

Simple Analogy

Entering a building requires showing your badge at the door once. After that you walk between rooms freely all day. The Once Only Controller is the badge check at the door.

The subtlety of nesting

The controller counts iterations of its looping parent. When it sits directly under the thread group, it runs once per thread for the whole test. When it sits inside a Loop Controller, it runs during the first pass of that loop each time the loop is entered afresh. This behaviour surprises teams that place the controller deep in the tree and expect a once-per-user guarantee.

ANTI-PATTERN-01 Avoid
Problem

Placing the Once Only Controller inside a repeating controller and assuming it will execute only one time for the entire test.

Why It’s Harmful

The login or setup runs again each time the parent loop restarts, inflating authentication traffic and producing session behaviour real users never show.

Correct Approach

Keep the Once Only Controller directly under the thread group for true once-per-user behaviour, or use a thread-group-level setup mechanism for work that must happen strictly once.

i
Alternative

For work that must run exactly once before any user starts, such as creating test data, a dedicated setup thread group is clearer and safer than a Once Only Controller.

7Interleave, Random and Random Order Controllers

Three ways to vary what each user does, each with a different promise.

Real users do not all follow an identical path. Some browse laptops, others browse phones, and the same user may switch between categories. These three controllers introduce variety, but they differ in whether the variety is predictable, truly random, or applied to ordering only.

1

Interleave Controller

Runs exactly one child per pass and moves to the next child on the following pass, cycling in order. It is predictable and gives each child an equal share over time. An option allows it to rotate across all threads instead of per thread.

2

Random Controller

Runs exactly one child per pass, chosen at random. Over a very large number of passes the shares even out, but in short tests the spread can look uneven, which is normal for randomness.

3

Random Order Controller

Runs every child on each pass, but shuffles the order. Nothing is skipped; only the sequence changes. It suits steps that are independent of each other.

sequenceDiagram
    participant U as Virtual User
    participant I as Interleave Controller
    participant A as Child A
    participant B as Child B
    participant C as Child C
    U->>I: Pass 1
    I->>A: Run A
    U->>I: Pass 2
    I->>B: Run B
    U->>I: Pass 3
    I->>C: Run C
    U->>I: Pass 4
    I->>A: Run A again
    Note over I: Rotation continues in order
        
FIG 3 — Interleave rotates through its children one pass at a time

Choosing among them

NeedBest ControllerReason
Exact even share of each pathInterleaveDeterministic rotation, easy to verify
Natural, unpredictable varietyRandomMimics visitors choosing freely
All steps done, order variesRandom OrderNothing skipped, sequence shuffled
Weighted share such as 70 and 30Throughput ControllerThese three give equal weights only
!
Common Mistake

Expecting Interleave or Random to produce unequal shares. They give each child the same weight. To model seventy percent and thirty percent, use the Throughput Controller in percentage mode, described next.

8The Throughput Controller

Despite its name, it controls proportion, not request rate.

The Throughput Controller decides how often its children run, in one of two modes. In total executions mode it lets the children run a fixed number of times and then stops running them. In percent executions mode it runs the children for a chosen percentage of the passes that reach it. A checkbox lets the counting be done per user instead of across all users together.

The name misleads many people, because it sounds like a control on requests per second. It is not. It never changes the speed of the test. It only decides in what share of passes a group of requests is included, which makes it the right tool for modelling workload mix, such as ninety percent of visitors only browsing and ten percent going on to purchase.

Simple Analogy

Imagine a cinema that shows the same short advertisement before only thirty percent of its screenings. The controller does not change how fast people arrive; it changes which screenings include the extra item.

Modelling a realistic mix

A common design places the main browsing steps directly in the journey, then wraps the purchase steps in a Throughput Controller set to a small percentage. Every user browses, and only the stated share continues to checkout. Because the percentage refers to passes, with many passes the observed share converges toward the target, while a very short test may deviate noticeably.

Browse-heavy shopping mix

A retailer’s data shows that ninety-five percent of visits end without a purchase. The test wraps the checkout steps in a Throughput Controller at five percent, so that the payment service receives a realistic, light share of traffic.

Limiting a heavy operation

A report-generation request is expensive, so the team places it under a Throughput Controller in total executions mode to run it only a handful of times during the whole test.

ANTI-PATTERN-02 Avoid
Problem

Using the Throughput Controller to achieve a target of requests per second.

Why It’s Harmful

The controller changes the share of passes, not the pace. The team thinks they have capped the rate while the real rate still depends on threads, response time and pauses.

Correct Approach

Use a throughput-shaping timer or an arrival-rate thread group for rate targets, and keep the Throughput Controller for workload proportions.

9The Runtime Controller

A controller that thinks in seconds instead of passes.

The Runtime Controller keeps running its children, pass after pass, until a set number of seconds has elapsed. Once the time is used up it stops running its children and lets the rest of the journey continue. It is a handy way to say “spend about two minutes doing this activity” without computing how many loops that would take.

This is different from the thread group scheduler, which ends the whole thread after a duration. The Runtime Controller ends only its own branch, and the thread carries on with whatever comes next. That makes it excellent for modelling a user who browses for a period and then moves to a different phase of activity.

Simple Analogy

A shopper tells a friend, “I will wander around the clothes section for twenty minutes and then go to the checkout.” The twenty-minute rule governs only the clothes section, not the whole day.

i
Practical Tip

Always include a realistic pause inside a Runtime Controller. Without one, the children repeat as fast as the server answers, and the branch becomes a tight loop that hammers the system for the entire period.

!
Common Mistake

Assuming the controller interrupts a request already in flight. It checks the time between passes, so the last pass can overrun the limit by the length of one pass.

10The If Controller

Run these requests only when a condition holds.

The If Controller evaluates a condition and runs its children only when the result is true. Real applications are full of such branches: the checkout page appears only when the basket has items, the admin menu only for administrators, a retry only after a failure. The condition normally refers to a variable that an earlier request has extracted from a response, so the flow adapts to what the server actually said.

flowchart TD
    A["Previous request completes"] --> B["Extractor stores value in a variable"]
    B --> C{"If Controller condition true?"}
    C -- "Yes" --> D["Run child requests"]
    C -- "No" --> E["Skip children"]
    D --> F["Continue to next element"]
    E --> F
        
FIG 4 — The If Controller branches on data returned by the server

Condition evaluation and performance

By default the controller can interpret the condition as a script expression, which uses an embedded scripting engine on every evaluation. That is slow when thousands of threads evaluate it constantly. A tick box lets you interpret the condition as a variable expression instead, which means the text must produce a plain true or false and is evaluated far more cheaply. For load tests this second mode is the recommended choice.

Children and the evaluation scope

Another option decides whether the condition is evaluated once before the first child or again before each child. Re-evaluating for every child is useful when an earlier child changes the data that the condition depends on, but it adds cost and can produce partial execution of the group. Be deliberate about which behaviour you want and document the choice.

Retry only after a failure

After a payment request, a condition checks whether the response indicated a temporary error. Only then does the controller run a short retry sequence, so successful payments do not trigger needless extra traffic.

!
Common Mistake

Writing a condition that compares text without quotation, or leaving a variable undefined. The condition then fails to evaluate or silently turns false, and an entire branch is never exercised while the report looks healthy.

11The While Controller

Keep repeating until the condition turns false.

The While Controller repeats its children for as long as its condition is true, and checks the condition before each repetition. It fits situations where the number of repeats is not known in advance, such as polling a job status until it reads complete, or following a chain of pages until no next page exists. It is the closest JMeter has to an ordinary programming loop.

Simple Analogy

A cook keeps checking the oven every few minutes until the cake is done. The cook does not know in advance whether that takes two checks or six, only the rule for when to stop.

Special conditions

If the condition is left blank, the controller continues until the last sampler before it fails. A special keyword also exists to refer to the success of the last sample inside the loop. These shortcuts are convenient for retry designs, but they depend on the exact position of the previous sampler, so reordering the tree can change the behaviour in ways that are hard to notice.

ANTI-PATTERN-03 Avoid
Problem

A polling loop with no pause and no upper bound on the number of attempts.

Why It’s Harmful

If the awaited condition never happens, the thread loops forever at full speed, flooding the server with status checks and producing a huge number of meaningless samples.

Correct Approach

Add a realistic pause between polls and include a counter in the condition so the loop ends after a reasonable maximum number of attempts, recording a clear failure when the limit is hit.

Waiting for an asynchronous job

A user uploads a file and the system processes it in the background. The test polls a status request every few seconds inside a While Controller until the status reads complete, mirroring how a real page would refresh.

12The Switch Controller

Choose exactly one branch based on a value.

The Switch Controller runs one of its children, selected by a switch value. The value can be a number that points to a child by its position, counting from zero, or the name of a child. When the value is numeric and falls outside the available range, the first child runs, which makes that first child the natural default branch.

It resembles the Interleave Controller because both run one child per pass, but the difference is who chooses. Interleave rotates blindly, whereas Switch obeys a value that you supply, often drawn from a data file or a variable. This makes it the right tool for data-driven journeys, where each row of test data says which type of action the user should perform.

Simple Analogy

A railway signal box moves a lever and sends the train down one specific track. The lever position is the switch value, and each track is a child branch.

flowchart LR
    V["Switch value from data row"] --> S{"Switch Controller"}
    S -- "0 or out of range" --> A["Default branch"]
    S -- "1" --> B["Branch for browse"]
    S -- "2" --> C["Branch for search"]
    S -- "3" --> D["Branch for purchase"]
        
FIG 5 — A switch value selects exactly one branch

Data-driven user types

A data file lists users with a column naming their role. The Switch Controller reads that column and sends administrators, customers and guests down three different journeys within one thread group.

i
Practical Tip

Give each child a clear, descriptive name and prefer name-based selection when the plan will be maintained by several people. Position numbers break silently when someone reorders the children.

13The ForEach Controller

Repeat the same steps once for every item the server returned.

Many responses contain lists: product identifiers, order numbers, search hits. A post-processor can extract all of them into numbered variables that share a common prefix, such as the first item, the second item and so on. The ForEach Controller then walks through those numbered variables and runs its children once for each, placing the current item into an output variable that the child requests can use.

You tell the controller the shared prefix, the starting and ending index and the name of the output variable. An option adds an underscore between the prefix and the number, matching the usual naming used by extractors. When a variable for the next number does not exist, the loop ends, which is how it knows the list has run out.

1

Request returns a list

A search or catalogue request returns many identifiers in its response.

2

Extractor stores them

A post-processor saves every match into numbered variables sharing one prefix.

3

ForEach walks the list

The controller runs its children once per variable, exposing the current item through the output variable.

4

Children use the item

A detail request uses the current identifier, producing realistic follow-up traffic for each result.

!
Common Mistake

Forgetting to limit the loop when a response returns a very long list. A search returning hundreds of items can turn into hundreds of follow-up requests per user, far beyond what a person would click.

14The Transaction Controller

Measure a business step, not just its individual requests.

Opening a single web page often triggers many requests: the page itself, stylesheets, scripts and images. Business stakeholders do not care about each of them; they care how long “open the home page” took. The Transaction Controller groups a set of requests and reports a combined timing, so that reports speak in business terms such as “login” or “checkout”.

It has two reporting styles. In the first, it generates a parent sample that wraps its children, and the individual child samples are hidden from summaries. In the second, it still lets all children appear and adds a separate summary record. An additional option decides whether the time spent in timers and processors inside the group counts toward the transaction time. Choosing wrongly can make a transaction look slower than the server really was.

Simple Analogy

A courier delivering a parcel makes many small stops along the way, but the customer only cares about the total time from dispatch to doorstep. The Transaction Controller reports the doorstep time.

The counting trap

When parent samples are generated, throughput and sample counts are based on the transactions, not the individual requests. A summary may therefore show far fewer samples than expected, and a request-per-second figure that looks low. Teams comparing with a server-side request count can be misled unless they understand which unit is being counted.

ANTI-PATTERN-04 Avoid
Problem

Including think time pauses inside a transaction while counting timer time toward the reported duration.

Why It’s Harmful

The transaction reports the deliberate pause as if it were server delay, producing inflated response times and false alarms about slowness.

Correct Approach

Place think-time timers outside the transaction, or leave the timer-inclusion option off, so the reported time reflects the system’s behaviour only.

Reporting checkout as one number

A team groups the address, shipping, payment and confirmation requests into a single transaction named Checkout, so the dashboard shows one clear timing against a business target.

15Reuse: Module Controller, Include Controller and Test Fragment

Write a step once and use it in many places.

Large test plans repeat certain steps in many journeys: logging in, adding to a basket, searching. Copying them everywhere makes maintenance painful, because a change has to be repeated in every copy. JMeter solves this with reuse elements that work like a library of shared building blocks.

Library

Test Fragment

A holder for reusable elements. It does not run on its own; it only runs when something else points to it.

Same Plan

Module Controller

Points to a controller elsewhere in the current plan and runs it as if it were placed here.

Other File

Include Controller

Loads the contents of an external plan file at runtime and runs them in place.

How they work together

You place the shared steps inside a Test Fragment, because that keeps them from running by themselves at the wrong moment. Then any journey that needs those steps adds a Module Controller that points at the shared block. For a library shared across many plan files, the Include Controller pulls in a separate file. Changing the shared block once changes every journey that uses it.

flowchart TD
    F["Test Fragment: shared login steps"]
    J1["Journey A"] --> M1["Module Controller"]
    J2["Journey B"] --> M2["Module Controller"]
    J3["Journey C"] --> M3["Module Controller"]
    M1 -.-> F
    M2 -.-> F
    M3 -.-> F
        
FIG 6 — Several journeys reuse one shared block through Module Controllers
!
Common Mistake

Renaming or deleting the shared block and forgetting that other journeys point to it. Module Controllers reference the target by its position and name in the tree, so reorganising it can break the reference or redirect it to the wrong block.

i
Maintenance Tip

Use consistent, stable names for shared blocks and keep them near the top of the plan. A short note beside each fragment explaining who uses it saves hours of detective work later.

16The Critical Section Controller

When only one user at a time may do something, this controller is the gatekeeper.

The Critical Section Controller ensures that its children are run by only one thread at a time. Each controller is given a lock name, and threads that reach controllers sharing the same name queue up and enter one by one. The purpose is protection: some operations must not overlap, such as updating a shared record, using a one-time token, or touching a resource that the application cannot handle concurrently.

Simple Analogy

A single-person changing room in a shop. Only one customer may enter at a time and everyone else waits outside until the door is free.

The cost of serialisation

By forcing threads to wait in line, the controller deliberately reduces concurrency in that region. If the protected section is long, many threads spend their time waiting, and the measured response times then include waiting that real users would not experience. Use it only for the shortest possible set of requests and only when correctness truly demands it.

!
Distributed Limit

The lock protects threads within one JMeter process. In a distributed test with several injectors, each injector has its own lock, so threads on different machines can still run the protected section together.

17Scope: How Controllers Shape Timers, Assertions and Processors

The position of a controller silently decides which helpers apply to which request.

Every helper element in JMeter attaches to the tree and reaches the elements around it according to fixed rules. Timers apply to all samplers in their scope, so a timer placed under a controller delays every request in that branch, and a timer placed at thread group level delays every request in the group. Assertions, pre-processors and post-processors follow the same logic, applying to the samplers in their branch. Config elements such as header and cookie managers also reach everything beneath their parent.

ElementWhere PlacedAffects
TimerUnder a samplerThat request only
TimerUnder a controllerEvery request in that branch
TimerUnder the thread groupEvery request in the group
AssertionUnder a controllerEvery request in that branch
Post-processorUnder a samplerThat response only
Config elementUnder a controllerRequests in that branch

Practical consequences

Because of scoping, restructuring a plan is never purely cosmetic. Moving a request out of a Transaction Controller may remove an assertion. Wrapping requests in a new controller may expose them to a timer that was intended for a different phase. After any significant restructuring, review the effective helpers on each request, and run a small rehearsal to confirm that pauses and checks are still where you expect.

i
Good Habit

Prefer placing helpers on the lowest sensible level of the tree, with controllers as folders that carry their own timers and assertions. This keeps each business step self-contained and makes moves between journeys far safer.

!
Common Mistake

Placing a think-time timer at the thread group level and then adding a second timer inside a controller. The delays add up, doubling the pause in that branch and halving the load without anyone noticing.

18Combining Controllers into a Realistic Workload Model

The real power appears when controllers are nested deliberately.

Individual controllers are simple. The art lies in combining them so the resulting traffic resembles actual visitors. Start from analytics and write the model in plain words before touching the tool: every user lands on the home page, searches or browses categories, views several products, sometimes adds to the basket, and occasionally purchases. Each phrase in that sentence suggests a controller.

flowchart TD
    TG["Thread Group"] --> O["Once Only: Login"]
    TG --> T1["Transaction: Home page"]
    TG --> R["Random Controller: Browse or Search"]
    R --> B["Browse categories"]
    R --> S["Search products"]
    TG --> L["Loop Controller: View several products"]
    L --> P["Product detail request"]
    TG --> TC["Throughput Controller at 10 percent"]
    TC --> CK["Transaction: Checkout"]
        
FIG 7 — One thread group expressing a layered, realistic user journey

Reading the model

1

Once Only for the setup

Login happens a single time per user, as it does in real life.

2

Random for the choice

Each pass the user either browses or searches, giving natural variety across the population.

3

Loop for the repetition

The user views several products, a count taken from analytics rather than guessed.

4

Throughput for the proportion

Only a small share continue to checkout, matching the real conversion rate.

5

Transaction for the measurement

Key business steps are reported as single, named timings suitable for dashboards and targets.

“Controllers turn a list of requests into a story about how people actually behave.”

19Performance, Security and Monitoring Considerations

Controllers cost resources, and the test plan must itself be trustworthy.

Cost on the load generator

Controllers are lightweight, but certain features are not. Condition evaluation through a scripting engine, repeated on every pass by thousands of threads, can consume noticeable processor time. Very deep nesting increases the work of walking the tree. Heavy use of transactions with parent samples creates additional records that must be stored and processed. The guiding principle is to keep logic cheap and keep the injector’s own overhead out of the measurement.

Observability of the plan itself

A controller that silently skips a branch is a quiet source of bad data. A condition that is always false means a whole business flow is never tested, yet the report shows green results. Protect against this by checking sample counts per transaction after every run. If the checkout transaction is supposed to run in five percent of passes and the counts show zero, the plan has a bug even though no errors appeared.

Check 1

Sample counts per branch

Confirm each branch ran roughly the expected number of times, especially conditional and proportional branches.

Check 2

Loop termination

Verify every loop and polling construct ends, and review samples for runaway repetition.

Check 3

Injector health

Watch the load generator’s processor and memory to be sure the logic is not the bottleneck.

Check 4

Server-side correlation

Compare request counts at the server with the plan’s design to confirm the flow matches intent.

Security of test plans

!
Do Not Skip

Controllers can create unbounded loops and heavy repetition. Run such plans only against systems you own or are authorised to test, use dedicated test accounts, and keep credentials in controlled storage rather than hardcoded inside conditions or shared fragments.

20Best Practices and More Anti-Patterns

The habits that separate maintainable plans from tangled ones.

Advantages of controller-based design

  • Models realistic proportions, loops and branches
  • Keeps plans readable through named folders
  • Enables reuse and one-place maintenance
  • Supports business-level reporting with transactions
  • Reduces duplication and copy-paste errors

Risks and trade-offs

  • Deep nesting becomes hard to reason about
  • Silent skipping can hide untested flows
  • Scope surprises when elements are moved
  • Condition evaluation can cost processor time
  • Misread counts when transactions hide samples

Habits to adopt

1

Design on paper first

Write the user journey and proportions in words, then map each phrase to a controller before building.

2

Keep nesting shallow

Prefer a clear two or three levels over a deep maze. Use reuse elements to hide complexity instead of nesting it.

3

Name by business meaning

Let folder and transaction names read like the business process, so reports are self-explanatory.

4

Rehearse with a tiny run

Run a single thread for a few iterations and inspect the tree results to confirm the flow before scaling up.

5

Verify counts after every run

Check that each branch executed in the expected proportion, then trust the timing results.

ANTI-PATTERN-05 Avoid
Problem

Building one giant flat journey with many conditional controllers stacked inside each other.

Why It’s Harmful

Nobody can predict which path a user takes, bugs hide in rarely visited branches, and changes ripple unpredictably through the plan.

Correct Approach

Split behaviour into separate journeys or thread groups with clear weights, and use Module Controllers to share common steps.

ANTI-PATTERN-06 Avoid
Problem

Using a Random Controller where the real traffic has known unequal proportions.

Why It’s Harmful

Each branch receives equal weight, so a rare action such as purchase gets as much traffic as a common one such as browsing, distorting the load on downstream services.

Correct Approach

Model unequal shares with Throughput Controllers in percentage mode, or with separate thread groups sized to the observed mix.

21Real-World Scenarios

The same few controllers solve very different problems.

Online retailer before a seasonal sale

The team builds one journey with a Once Only login, a Random browse-or-search choice, a Loop over product views and a Throughput Controller for the small share that buys. Transactions wrap home page, search and checkout so the dashboard shows business-level timings.

Bank portal with different user roles

A data file names each user’s role, and a Switch Controller sends customers, tellers and administrators down role-specific journeys that share a common login block through a Module Controller.

Document upload with background processing

A user uploads a file, and a While Controller polls the job status with a pause and a maximum attempt count until processing completes, mirroring how a real page would refresh.

Search results with follow-up clicks

A ForEach Controller walks through the first few result identifiers extracted from a search response and opens each detail page, producing the cascading traffic that a real user generates.

Inventory update that must not overlap

A request that updates a shared stock counter is wrapped in a Critical Section Controller so only one thread performs it at a time, preventing artificial conflicts that would never occur in a correctly designed application.

Government form near a deadline

A Runtime Controller keeps simulated citizens filling forms for a fixed period, followed by a submission step, with realistic pauses throughout to mimic people reading instructions.

22Frequently Asked Questions

Q1What is the difference between a sampler and a logic controller?

A sampler sends a request to a server and records the result. A logic controller sends nothing; it only decides which of its children run, in what order and how many times.

Q2How is a Loop Controller different from the thread group loop count?

The thread group loop repeats the entire user journey. A Loop Controller repeats only the steps inside it within a single pass. When both are used, the repeats multiply.

Q3Does the Throughput Controller limit requests per second?

No. It controls the proportion or total count of executions of its children. For rate control, use a throughput-shaping timer or an arrival-rate thread group.

Q4How do I model seventy percent browsing and thirty percent searching?

Place each activity under a Throughput Controller in percentage mode with the matching share, or use separate thread groups sized in that ratio. Random and Interleave give equal weights and cannot do this alone.

Q5Why does my Once Only Controller run more than once?

It counts iterations of its looping parent. Inside a Loop Controller it runs again each time that loop restarts. Place it directly under the thread group for strict once-per-user behaviour.

Q6Which condition mode is best for the If Controller?

Interpreting the condition as a variable expression is cheaper than using a scripting engine and is generally recommended under heavy load, provided the expression yields a clear true or false.

Q7How can I stop a While Controller from running forever?

Include a counter or an attempt limit in the condition and add a pause between repetitions, so that the loop ends after a sensible maximum and does not flood the server.

Q8When should I use Switch instead of Interleave?

Use Switch when a supplied value, such as a data column, should choose the branch. Use Interleave when you simply want each branch to take its turn in order.

Q9Why do my sample counts look lower after adding a Transaction Controller?

When parent samples are generated, the reports count transactions rather than individual requests, so totals and throughput are expressed in business steps. Compare like with like when checking against server counts.

Q10What is a Test Fragment for?

It holds reusable elements without running them on its own. Module Controllers or Include Controllers point to it, so the shared steps run only where you ask for them.

Q11Does the Critical Section Controller work across several injectors?

Its lock operates within one JMeter process. Each injector holds its own lock, so threads on different machines can still enter the protected section at the same time.

Q12Why does moving a request into a controller change my results?

Scope follows the tree. The request may now fall under different timers, assertions, processors or config elements, which changes pauses, checks and headers applied to it.

23Summary and Key Takeaways

Logic controllers are the grammar of a JMeter test plan. Samplers are the words, but controllers decide the sentences: what repeats, what is chosen, what is conditional, what is shared and what is measured as a unit. Understood well, they let a few dozen elements describe a convincing population of users and keep the plan maintainable for years. Understood badly, they quietly skip branches, multiply load, distort timings and produce confident reports about traffic that never existed.

Key Takeaways

  • Controllers decide flow, samplers do work — a controller sends no request itself; it governs which children run, in what order and how often.
  • Match the controller to the question — repeat with Loop, While or ForEach, choose with Switch, Random or Interleave, make conditional with If, Once Only or Throughput.
  • Throughput means proportion, not pace — use it for workload mix and use timers or arrival-rate groups for requests per second.
  • Mind the multiplication and the scope — nested loops multiply work, and the position of a controller decides which timers, assertions and processors apply.
  • Measure in business terms — Transaction Controllers give meaningful timings, but remember they change what is counted and keep pauses outside them.
  • Reuse instead of copying — Test Fragments with Module or Include Controllers keep shared steps in one maintainable place.
  • Verify the plan, not just the server — after every run check per-branch counts and loop termination so silent skipping never passes as success.