Controllers: Simple, Loop, Transaction, If, While, Switch, and Throughput: Configuration, Design Patterns, and Trade-Offs
Controller design should make the workload easier to reason about, not merely shorter. Every nesting level should represent a business boundary, repetition rule, branch decision, or reporting transaction whose effect can be predicted from the tree.
Learning objectives
- Choose a flat or nested tree based on business boundaries and scope clarity.
- Choose Thread Group loops or nested Loop Controller from what repeats.
- Use boolean variable/JEXL3/Groovy condition styles safely.
- Select additional or parent Transaction Controller reporting deliberately.
- Choose Throughput Controller percentage or total executions from branch requirements.
- Keep branch population data separate from control implementation when maintainability demands it.
1. Mandatory executable path remains local
http://127.0.0.1:8000, ≤2 threads, bounded
loops.
Remote engines, containers, paid load platforms, enterprise
identity, and managed telemetry are not required.
2. Flat tree versus nested controllers
| Flat tree | Nested controllers |
|---|---|
| Easy to scan for very short fixed sequences. | Expresses business phases, repetition, and branch scope. |
| Fewer levels reduce navigation overhead. | Makes timer/assertion/config scope more intentional. |
| Can hide why repeated requests exist. | Can become unreadable if every request gets its own controller. |
| Good for simple linear health checks. | Good for multi-step journeys with meaningful boundaries. |
Use the shallowest tree that still exposes the scenario's real control semantics. Nesting for aesthetics alone adds maintenance cost.
3. Thread Group loop versus Loop Controller
Use Thread Group loop count for whole-journey repetition. Use Loop Controller when only a subsection repeats.
Example: “browse once, perform three searches, checkout once” belongs naturally to one outer iteration containing SearchLoop ×3. Setting Thread Group loops=3 would repeat browse and checkout three times too.
4. If Controller condition style
Preferred choices:
- direct boolean variable:
${DO_CHECKOUT}; - JEXL3:
${__jexl3(${CART_TOTAL} > 0)}; - Groovy function for conditions not cleanly expressible otherwise.
Keep Interpret Condition as Variable Expression? enabled. Avoid JavaScript mode for load tests because current documentation warns it can have a very large performance penalty.
Unless the business rule can change between child elements, leave Evaluate for all children off; reevaluating before every child adds complexity and can make a branch partially execute when its condition mutates mid-controller.
5. While condition style and hard bounds
Use a variable whose producer is clear: response extraction, explicit counter state, or a stable property. Because the While condition is evaluated twice per cycle, avoid side-effecting functions in the condition itself.
For production-like polling, pair the business stop condition with a safety bound: target-side timeout, max attempts encoded by a separate counter/controller, and/or Thread Group duration. Infinite polling is never an acceptable “wait until ready” strategy.
6. Switch numeric index versus named branches
Numeric indexes are concise but brittle to reordering. Named
children such as standard, express, and
default preserve intent. Name matching is
case-sensitive except the special default fallback.
Use a variable such as ${CHECKOUT_MODE} supplied by
test data/configuration rather than embedding multiple
environment-specific conditions into the tree.
7. Additional transaction sample versus parent sample
| Additional sample (parent OFF) | Generate Parent Sample ON |
|---|---|
| Child samples remain normal JTL rows. | CSV JTL normally contains parent transaction rows, not separate child rows. |
| Transaction adds one extra reporting row. | Avoids naive child+parent double counting in flat CSV metrics. |
| Easy endpoint + transaction analysis in same CSV. | Needs sub-sample-aware debugging/XML if child details must be persisted. |
| Can inflate 'sample count' if labels aren't filtered. | Can hide child-level CSV detail if analysts expect it. |
Choose based on your reporting pipeline. Never switch modes between baselines without documenting that result schema changed.
8. Transaction timing: endpoint sum versus business wall time
By default Transaction Controller excludes timers and pre/post-processor processing from generated transaction time. Enable “Include duration of timer and pre-post processors” only when the metric explicitly intends that broader generator-side journey duration.
That option changes measurement semantics, not target behavior. Record it with the result artifact.
9. Throughput Controller percentage versus total executions
| Requirement | Mode | Reason |
|---|---|---|
| Optional branch in ~30% of opportunities | Percent executions | Models population/branch frequency. |
| Exactly 2 branch executions in a bounded test | Total executions | Deterministic count. |
| Exactly 2 executions per user | Total executions + Per User ON | Count is maintained for each thread. |
| Exactly 2 executions globally in one engine | Total executions + Per User OFF | Shared controller count across local users. |
| 2 requests/s | Neither | Use the workload/rate tools from Chapter 7. |
10. Per User changes the counting domain
With Total Executions=2:
- Per User off + 5 threads → at most 2 executions total in that JMeter instance.
- Per User on + 5 threads → up to 10 executions (2 per thread).
In distributed mode each remote engine has its own JMeter process/controller state, so a “global” count across all remote engines requires explicit architecture/aggregation; do not assume controller counters synchronize across machines.
11. Explicit branch data versus control logic
Store population attributes such as DO_CHECKOUT,
CHECKOUT_MODE, or user cohort in Chapter 11
data/configuration. Controllers should implement the branch, not
invent the population.
This separation improves reviewability: changing the business mix can often mean changing data/property inputs rather than restructuring the JMX.
12. Nested scope versus accidental inheritance
A Timer or Assertion attached above a controller applies to descendant samplers in its scope. Deep nesting can make inheritance hard to see. Use Simple Controller to establish explicit business boundaries when a scoped assertion/timer should affect a subset of samples.
13. Controller design affects measurement validity
Wrong loop multiplication changes offered load. Expensive If scripts reduce generator headroom. Transaction parent mode changes JTL schema. Throughput Controller percentages change business mix. While loops can create excess traffic. Every controller change therefore belongs in the run manifest and baseline comparison.
For every meaningful comparison, preserve the raw JTL together with
its matching jmeter.log, the exact JMX/controller
settings, and independent target request counts. The JTL proves
which labels/results were emitted; jmeter.log preserves
engine/runtime and condition-evaluation diagnostics; target counts
prove which controller decisions became real protocol traffic.
14. Keep controller logic separate from surrounding layers
| Layer | Examples | Not a substitute for |
|---|---|---|
| JMeter controller | branching, loops, grouping, transaction result | Target business workflow correctness. |
| JVM/generator | CPU, GC, expression engine cost | Fixing an infinite While condition. |
| Timer/workload | arrival/think/pacing | Throughput Controller branch percentage. |
| SUT | endpoint latency, queues, state transitions | Transaction parent/child reporting mode. |
| CI/container | runner limits, artifact parsing | Stable JTL label/schema design. |
| Distributed engine | engine-local controller counters | A synchronized global branch counter. |
15. Decision table
| Need | Controller choice | Evidence |
|---|---|---|
| Group a phase only | Simple Controller | Target/sample counts unchanged. |
| Repeat just search three times | Loop Controller ×3 | Search label/server path count. |
| Skip checkout for ineligible user | If Controller with boolean variable | Branch variable + checkout count. |
| Poll until target says complete | While + response-derived boolean + hard bound | Poll count + extracted state + stop evidence. |
| Choose standard/express path | Switch with named child | Switch variable + one selected label. |
| Measure whole checkout | Transaction Controller | Transaction label + child/server counts. |
| Run optional branch in population | Throughput Controller percent/total | Branch opportunities vs actual executions. |
Knowledge check
Why should Thread Group loops not replace SearchLoop in a browse-once/search-three-times journey?
Thread Group loops would repeat the entire journey, including browse and checkout, rather than only the search subsection.
What is the preferred If condition mode for scalability?
Interpret Condition as Variable Expression, using a boolean variable or JEXL3/Groovy expression.
What reporting trade-off comes with Transaction parent mode?
CSV JTL becomes simpler at transaction level but child samples are no longer separate CSV rows.
What does Throughput Controller Per User change?
Whether execution count/percentage is tracked independently per thread or globally in that JMeter instance.
Why should branch population live in data/config rather than many hard-coded Ifs?
It separates workload population from control implementation, improving portability, reviewability, and CI reproducibility.
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.