Functions, Custom Properties, Reusable Fragments, and Modular Plans: Configuration, Design Patterns, and Trade-Offs
Reuse is beneficial only when the boundary represents stable behavior. Over-modularization can be as hard to understand as duplication: a single request may require following five includes, twelve properties, and nested functions before the effective workload is visible. The design goal is not maximum reuse; it is minimum duplication with explicit ownership and predictable runtime assembly.
Learning objectives
- Choose function, variable, or property based on ownership and evaluation time.
- Choose Module or Include Controller based on same-plan versus cross-plan reuse.
- Choose one JMX or several modules based on dependency and release boundaries.
- Choose relative paths or explicit project-root resolution deliberately.
- Choose generated random data or committed/generated deterministic fixtures from replay needs.
- Connect modular choices to configured/achieved workload and generator/target evidence.
1. Mandatory path stays local/free
127.0.0.1:8020, ≤2 threads
×3 work loops.
Managed load clouds, shared performance environments, paid CI,
enterprise identity, Kubernetes, remote RMI, external APIs, and
third-party plugins are optional contexts only.
2. Function versus variable versus property
| Need | Prefer | Reason |
|---|---|---|
| Read a run/load knob in a field | __P / property |
Process-wide configuration supplied from property file or
-J.
|
| Store response/session/user state | variable | Thread-local ownership and ordinary extractor/config semantics. |
| Compute small context-dependent value | built-in function | No extra component/code when evaluation semantics are clear. |
| Reuse the computed value many times | function with reference variable or explicit producing element | Avoid repeating an opaque function expression in many fields. |
| Change a property during run | rare/special only | Properties are global; runtime mutation can create races and hide configuration history. |
3. Module Controller versus Include Controller
| Module Controller | Include Controller |
|---|---|
| Targets a uniquely named controller already loaded in the same JMX. | Loads an external JMX/Test Fragment. |
| Best for reuse between scenarios inside one plan. | Best for reuse across multiple top-level plans/projects. |
| No external file deployment/path problem. | Adds file deployment, prefix/path, version and CI/remote-engine concerns. |
| Reference can be selected from controllers loaded in GUI. | Filename is fixed text; no variables/functions in Filename field. |
| Fragment naming uniqueness is critical. | Include Controller naming/path uniqueness and included fragment structure are critical. |
Start with Module when reuse is local to one plan. Move to Include when independent reuse justifies the extra file/path/dependency contract.
4. One JMX versus several modules
One JMX has fewer deployment/path problems and is easier for a beginner to trace. It becomes awkward when many scenarios duplicate a stable flow or teams need separate ownership/release cadence.
Several JMX modules reduce definition duplication and permit targeted ownership, but require dependency naming, file deployment, path resolution, and compatibility/version policy. Keep the module graph shallow and acyclic.
5. Relative paths versus explicit project-root resolution
| Relative path only | Project-root launcher / explicit prefix |
|---|---|
| Short and convenient when cwd/JMX launch path never changes. | Stable across arbitrary caller working directories. |
| Can break in CI/IDE/scheduler/remote engine when launch location differs. | Requires one launcher/config contract but makes resolved paths observable. |
| Failure may appear only after plan is moved. | Manifest can record exact project root/prefix before JMeter starts. |
The Chapter 20 mandatory solution uses an explicit launcher-derived
absolute includecontroller.prefix. The JMX keeps only
the stable filename session-bootstrap.jmx.
6. Property files versus command-line run overrides
Use a committed synthetic environment property file for stable
defaults such as localhost host/port and conservative load ceilings.
Use explicit -J values for run identity/variant or
intentional one-off overrides.
Do not place real credentials in either. JMeter property files and command manifests are ordinary files and can be committed/artifacted accidentally.
7. Explicit defaults versus fail-fast configuration
An explicit safe default makes a lab runnable. Production/release
plans sometimes need a stricter contract where a missing property
should fail before traffic. Since __P can silently
return 1 when no default is supplied, use one of these patterns:
- always specify an explicit conservative default and record resolved values;
- use a one-time preflight assertion/script to reject missing/unsafe properties before Thread Groups start;
- have the launcher validate required keys before invoking JMeter.
8. Generated data versus committed synthetic fixtures
Committed small synthetic fixtures are easy to review and reproduce. Generated data is useful when the dataset is large or must vary structurally, but the generator/version/seed/manifest becomes part of evidence.
For exact replay, prefer deterministic generation or a committed/generated manifest rather than unseeded random functions.
9. Randomness needs a reproducibility strategy
__Random(min,max) returns a pseudo-random number but
exposes no seed parameter. If exact numeric sequence matters, the
current Random Variable Config Element offers a Random Seed and Per
Thread option. Alternatively, pre-generate a seeded CSV dataset
outside load execution.
__UUID() is excellent for unique correlation when exact
replay is not required; do not confuse uniqueness with
reproducibility.
10. Keep function use readable
Prefer:
Property-backed defaults:
HOST = ${__P(target.host,127.0.0.1)}
PORT = ${__P(target.port,8020)}
RUN_ID = ${__P(run.id,p20-local)}
Sampler:
user=user-${__threadNum}
run_id=${__P(run.id,p20-local)}
over a single field containing nested conditionals, random values, time functions, properties and string concatenation. If a reviewer cannot predict the effective request without executing the expression, the plan is too opaque.
11. Fragment input/output contract
FRAG.session.bootstrap.v1 owns:
-
input: target defaults already available from top-level plan;
run.id/variantproperties; current__threadNum; - action: one POST session-create request;
- output: thread-local
SESSION_ID; - assertion: status created and SESSION_ID exists;
- does not own: thread count, pacing, Cookie Manager, run result files, cleanup, or global mutable properties.
This contract keeps the fragment reusable without hiding workload control.
12. Configuration-layer boundaries
| Layer | Examples | Do not confuse with |
|---|---|---|
| JMeter core/test plan | functions, properties, Test Fragment, Module/Include, variables, controllers | Java/JVM user.dir or OS filesystem permissions. |
| Java/JVM | user.dir, heap/GC, Java version |
Include controller business semantics. |
| OS/filesystem/network | path separators, cwd, file permissions, loopback sockets | JMeter property/variable ownership. |
| SUT | session/work behavior, service timing | Module definition duplication or include path errors. |
| Plugin/driver | none in mandatory lab | core Module/Include/functions. |
| CI/container | checkout path, working directory, mounted modules, workspace | production target capacity. |
13. Distributed-run implication
Remote engines do not share the controller's filesystem. External fragments, property files, and data required by a remote plan must exist consistently on every engine, and JVM-global properties are global only within each engine. Chapter 20 does not run RMI; it teaches the dependency contract now so Chapter 25 can apply it safely.
14. Worked scenario
Four top-level plans share a 6-step authentication bootstrap, but only two use the same cookie/config assumptions.
- Do not make one giant fragment controlled by many Boolean properties.
- Create a stable common bootstrap module only if its inputs/outputs truly match.
- Keep top-level Cookie Manager/UDVs/environment config in each main plan.
- Use Include for cross-plan reuse, unique names, explicit prefix/deployment policy.
- Keep plan-specific assertions/branch logic near the callers.
15. Decision table
| Requirement | Preferred choice | Evidence/validity |
|---|---|---|
| Same flow reused twice in one JMX | Test Fragment + Module Controller | No external path; same target/sample counts. |
| Same stable flow reused by many plans | External Test Fragment + Include Controller | Module version/path/dependency recorded. |
| Per-user session ID | variable | Thread isolation visible in target events. |
| Host/threads/loops | property + __P |
Resolved property manifest before load. |
| Exact replay of random numeric data | seeded Random Variable or pre-generated fixture | Seed/manifest retained. |
| Portable CI/local execution | project launcher + absolute resolved prefix | Two-cwd/clean-shell proof. |
16. Configured versus achieved load
Configured workload is resolved thread/loop/pacing properties plus
runtime controller placement. Achieved workload is the successful
session-create/work/delete sample count and target event
distribution. A modular refactor is performance-valid only when
generator path/module resolution succeeds and the target workload
remains equivalent. Preserve JTL + matching jmeter.log,
properties, command manifest, fragment graph, resolved prefix,
generator CPU/heap, target events, and duplication note.
Knowledge check
When is Module Controller preferable to Include Controller?
When reuse is inside one JMX and there is no need for a separately deployed fragment file.
What makes an external module a CI/distributed dependency?
The fragment file/path/version must exist consistently on every execution environment/engine.
How can exact random replay be improved?
Use Random Variable with an explicit seed/per-thread design or pre-generate deterministic data and preserve the manifest.
Why shouldn't a fragment own thread count/pacing?
Those define workload/arrival semantics and should remain visible in the calling top-level plan.
What distinguishes configured from achieved workload after modularization?
Configured values come from resolved properties/controllers; achieved evidence comes from successful samples and target event counts after module/path resolution.
Official references and version notes
- JMeter Component Reference — Module Controller — runtime substitution of an already loaded controller/fragment and unique fragment naming.
-
JMeter Component Reference — Include Controller
— external JMX/Test Fragment use, filename limitations,
includecontroller.prefix, and path fallback behavior. - JMeter Component Reference — Test Fragment — reusable fragment semantics with Module/Include Controllers.
- JMeter Functions — __P — simplified command-line property lookup and default behavior.
- JMeter Functions — __property — general JMeter property lookup and optional variable assignment.
- JMeter Functions — __threadNum — thread-group-local thread numbering and Configuration Element restrictions.
- JMeter Functions — __Random — pseudo-random values and optional variable assignment.
- JMeter Component Reference — Random Variable — seeded/per-thread reproducible random-number option.
-
JMeter Getting Started
—
-q/-J, property files, and JMeter configuration loading. - JMeter Best Practices — GUI authoring/debugging and non-GUI execution for load.
- Apache JMeter downloads — current stable release and Java requirement.
Version-sensitive statements were rechecked against current Apache
JMeter primary documentation on 2026-09-05. The course baseline
remains Apache JMeter 5.6.3 with a Java 17 JDK;
JMeter 5.6.3 requires Java 8+. A Module Controller substitutes an
already loaded controller/fragment into the active runtime path;
fragments referenced by Module Controller need unique names
because JMeter uses the controller/parent name path to find them
after reload. Include Controller is for external JMX/Test Fragment
content. Its Filename field does not support
variables/functions. The
includecontroller.prefix property can prefix the
filename; if prefix+filename cannot be found, JMeter attempts the
filename relative to the JMX launch directory. Top-level Cookie
Manager/User Defined Variables belong in the main test plan rather
than an included JMX because included copies are not guaranteed to
work as expected. __P(name,default) is intended for
command-line properties; without an explicit default it returns
1. __threadNum is local to its Thread
Group and should not be placed in Configuration Elements such as
User Defined Variables because those are processed by a separate
thread. For reproducible random numeric data, the Random Variable
Config Element exposes a seed/per-thread option; the simple
__Random function does not expose an equivalent seed
parameter.
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.