Controllers: Simple, Loop, Transaction, If, While, Switch, and Throughput: Core Concepts and Mental Model
Chapters 7–11 made timing, correctness, correlation, extraction, and data ownership explicit. Chapter 12 controls the shape of the journey itself: which steps execute, how many times they repeat, which branch is selected, and whether several child samples are also represented by a business-transaction sample.
Learning objectives
- Explain how logical controllers affect sampler order, branch inclusion, and repetition.
- Separate branch frequency from request-rate control.
- Predict sample counts when Thread Group and Loop Controller iterations multiply.
- Understand Transaction Controller child versus parent result labeling.
- Choose safe boolean expressions for If/While controllers.
- Inspect controller scope, result labels, and target request counts before changing a plan.
1. The practical problem: a flat script hides workload intent
A recorded browse → search → search → checkout → upsell sequence might look plausible as a flat list of HTTP requests, but it does not explain why search repeats, when checkout is skipped, which checkout mode is chosen, or whether upsell should occur for every user. Controllers turn those business rules into explicit test-plan structure.
The danger is that controller names can mislead. A Throughput Controller does not produce “100 requests per second.” A Transaction Controller can add a new reporting sample without sending any request. A While Controller can generate effectively unbounded traffic if its condition never becomes false.
http://127.0.0.1:8000, maximum 2 threads, bounded
loops, synthetic data, and explicit Thread Group duration/stop rules
for failure reproductions.
2. Mental model: thread → controller → sampler → result
Logical controllers decide order, repetition, or inclusion. Only executed samplers create target traffic; transaction samples are reporting artifacts rather than extra HTTP requests.
flowchart TD TG[Thread scenario / iteration] --> SC[Simple grouping] SC --> LC[Loop decision] LC --> IF[If / While / Switch decision] IF --> S[Executed sampler sequence] S --> T[Authorized local target] S --> TX[Transaction aggregation] TX --> J[JTL parent/additional transaction labels] S --> J2[JTL child labels] T --> SE[Independent server request counts] TC[Throughput Controller] --> IF R[Timer / assertion / processor scope] --> S
The thread enters its scenario. Controllers decide which child elements become runnable and how often. Only executed samplers create protocol traffic. A Transaction Controller observes the nested execution and creates a JMeter SampleResult; it does not create another server request. Timers, assertions, processors, and config elements still obey the tree scope around those children.
3. Simple Controller: organization without execution semantics
Simple Controller groups samplers/controllers so a plan can say “Browse phase” or “Checkout phase.” It does not add loops, branches, waits, or samples. Moving identical children into a Simple Controller should not change target request count by itself.
That makes it valuable for controlling scope: a timer or assertion attached to a Simple Controller can intentionally affect only that grouped business phase.
4. Loop Controller multiplies work inside the Thread Group
If Thread Group loop count is 3 and a child Loop Controller count is
2, one child request inside that controller executes
3 × 2 = 6 times per thread. The Loop Controller exposes
an index such as ${__jm__SearchLoop__idx}, starting at
zero.
Use a nested Loop Controller when one subsection repeats more often than the whole scenario. Use the Thread Group loop for the outer user journey.
5. If Controller: boolean inclusion
If Controller decides whether its children run. The preferred current pattern is to enable Interpret Condition as Variable Expression? and feed a variable containing true/false or a JEXL3/Groovy expression that resolves to true/false.
Example: ${DO_CHECKOUT} where Chapter 11 test data
supplied true or false. Avoid JavaScript
condition mode in load tests because current JMeter documentation
warns of a potentially large performance penalty.
6. While Controller: repeat until the state becomes false
While Controller repeats its children until its condition is the
string false (with special blank/LAST semantics). Its
condition is evaluated before the child sequence and again after it.
That is why non-idempotent functions such as counters should not be
placed directly in the condition.
A safer pattern is causal state: a poll sampler extracts
POLL_MORE=true/false, and the While condition simply
reads ${POLL_MORE}. Add an independent hard
duration/iteration guard for failure safety.
7. Switch Controller: select one child
Switch Controller chooses one subordinate element by numeric index
or name. Numeric children are zero-based. A non-numeric switch value
looks for a child with the same case-sensitive name; if none
matches, a child named default is used when present.
Named business branches such as standard and
express are often more maintainable than magic numeric
indexes because the intent survives reordering.
8. Throughput Controller controls branch execution—not throughput
Throughput Controller has two execution modes:
- Percent executions: execute the branch for a configured percentage of scenario iterations.
- Total executions: stop executing the branch after a configured count.
Its Per User setting decides whether the count/percentage is tracked per thread or globally across users. It does not schedule RPS. Chapter 7's Constant/Precise Throughput Timers are rate-oriented components.
9. Transaction Controller changes reporting semantics
A Transaction Controller measures the overall nested business operation and creates a transaction SampleResult. With Generate Parent Sample off, child samples remain normal JTL rows and an additional transaction row is generated after them. With Generate Parent Sample on, the transaction becomes the parent; child samples remain visible in View Results Tree but do not appear as separate CSV JTL rows.
The transaction is successful only if all nested samples are successful. By default its measured time excludes Timer and pre/post-processor processing; the optional include-duration setting changes that interpretation.
10. Transaction rows can make naive sample counting wrong
If a journey sends five HTTP requests and an additional-mode Transaction Controller wraps them, CSV JTL can contain six rows: five child request results plus one transaction result. The target still received only five requests.
Never compute “HTTP request throughput” by counting every JTL row without filtering transaction labels.
11. Controllers also create scope boundaries
A Timer, Assertion, Pre-Processor, or Post-Processor attached above a controller can apply to every sampler under it. A timer placed under a broad Checkout Simple Controller can delay both standard and express branches. A parent-mode Transaction Controller can also complicate assertion scope because assertions attached to it may affect child and parent samples.
Scope review therefore precedes controller refactoring.
12. State to inspect before changing controllers
| State | Question |
|---|---|
| Thread/workload | Thread count, outer iterations, duration, pacing, achieved load? |
| Controller state | Loop counts, While/If variables, Switch value, Throughput mode/per-user setting? |
| Variables/data | Which per-thread values drive branch decisions? |
| Protocol/session | Does skipping a branch leave required session state unset? |
| Scope | Which timers/assertions/processors/config elements are inherited by child samplers? |
| Results | Which labels are protocol child samples versus transaction reporting samples? |
| Generator | Are condition scripts/large trees/listeners adding CPU/GC cost? |
| Target | How many real requests reached each endpoint independently of JTL transaction rows? |
| Validity | Do configured branch percentages/counts produce the intended business mix? |
13. Read-only inspection first
- Export or screenshot the current controller tree before edits.
- Multiply Thread Group × nested Loop counts on paper.
- List every If/While/Switch/Throughput condition/value and expected branch.
- Mark Transaction Controllers as additional or parent mode.
- Count predicted child HTTP requests separately from predicted JTL rows.
- Inspect JTL labels and target/server counts from a tiny baseline run.
-
Record JMeter/Java versions, JTL, matching
jmeter.log, and generator CPU/memory.
14. DevOps connection
Controllers encode workload intent in a reviewable form: “search twice,” “checkout only for eligible users,” “poll until ready,” “select one checkout mode,” “show an upsell to a defined population,” and “measure checkout as a business transaction.” That makes CI performance failures attributable to scenario logic rather than opaque request lists.
Knowledge check
Does Simple Controller change sampler execution count?
No. It is organizational only; its children execute as they would without that grouping unless other scoped elements change behavior.
What is the result of Thread Group loop 3 × Loop Controller loop 2 for one child sampler?
Six executions per thread.
Why should Throughput Controller not be used as an RPS regulator?
It controls how often a branch executes by percentage or count; it does not schedule request throughput.
Why can parent-mode Transaction Controller make CSV JTL look like only one sample?
Child results become sub-samples and are not written as separate CSV rows; the parent transaction row represents the nested operation.
What is unsafe about a non-idempotent function inside While condition?
The condition is evaluated twice around each child cycle, so a counter-like function can advance unexpectedly and produce incorrect or unbounded looping.
Official references and version notes
- Component Reference — Logic Controllers — Simple, Loop, Throughput, If, While, Switch, and Transaction Controller semantics.
- Elements of a Test Plan — scope and execution-order rules for samplers, controllers, timers, processors, and assertions.
- Functions and Variables — current JEXL3/Groovy/function/variable behavior used in conditions.
- Best Practices — GUI authoring versus CLI load execution and generator-validity guidance.
- Apache JMeter downloads — current production release and Java requirement.
Version-sensitive behavior was checked against current Apache
JMeter primary documentation on 2026-09-05. The course baseline
remains Apache JMeter 5.6.3 with a Java 17 JDK
for labs and no third-party plugins; JMeter 5.6.3 requires Java
8+. Simple Controller is organizational only. Loop Controller
multiplies its loop count by the enclosing Thread Group iterations
and exposes an index variable named
__jm__<controller-name>__idx. If Controller
should normally use
Interpret Condition as Variable Expression with a
boolean variable or __jexl3/__groovy;
JavaScript condition mode has a potentially large performance
penalty. While Controller evaluates its condition before and after
its children, so non-idempotent functions such as counters in the
condition can produce surprising behavior. Switch Controller
selects one child by numeric index or name and has explicit
fallback semantics. Throughput Controller is intentionally
documented as badly named: it controls branch execution
count/percentage, not request throughput; use a throughput Timer
for rate control. Transaction Controller creates an additional
transaction SampleResult unless Generate Parent Sample is enabled.
In parent mode, child samples do not appear as separate CSV JTL
rows. By default transaction elapsed excludes timers and
pre/post-processor processing; the optional include-duration
setting changes that measurement.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.