skip to content

Three JMeter plans each need the same login flow. Would you reuse it with a Module Controller or an Include Controller?

level: principalimportance: should knowfreq 38%

answer

  1. Only one of the two crosses a file
  2. Ask what happens when it breaks
  3. One stops the run, one shrugs
  4. Included contents are invisible while editing

basics

~20 s

Put the login flow in its own .jmx behind an Include Controller when three separate plans must share one copy. Use a Module Controller when the reuse lives inside a single plan file and you want the target visible.

solid answer

~40 s

Only the Include Controller crosses a file boundary, so for three separate plans it is the mechanism that gives you **one** copy of the login flow rather than three. What that costs is visibility and failure behaviour: the included contents are not shown in the GUI, so nobody editing a plan can see what the branch does, and a file that cannot be loaded is logged while the run **continues**. A Module Controller is the better tool inside one plan file — the target is a node you can open, and an unresolved reference throws a stop-test exception rather than quietly dropping steps. A common shape uses both: `login.jmx` holds a Test Fragment plus a debug Thread Group whose Module Controller runs it, and the three plans each carry an Include Controller.

go deeper

for a junior

Know the one-line split: a Module Controller reuses something already in this plan, an Include Controller pulls a Test Fragment out of another .jmx file.

for a middle

Explain what the file boundary buys and costs — one copy for three plans, but contents you cannot see or edit from the plan that includes them.

for a senior

Bring the failure modes: an unresolved module reference stops the run, a failed include is logged and skipped, so the sharing mechanism is the one that can leave a plan green having done less.

for a principal

Own the decision and its obligations: pick the Include Controller for cross-plan reuse, then decide who owns the fragment file, how its directory is supplied per environment, and what proves its steps ran.

## The one difference that decides it A Module Controller resolves its target by walking the **plan tree it was loaded with**, matching a stored path of element names. It has no notion of another file. An Include Controller names a `.jmx` in its *Filename* field and splices in the subtree under the first Test Fragment in it. So for three separate plans that must not drift apart, the Include Controller is the only one of the two that keeps a single copy. Everything else is a trade you make with your eyes open. ## The trade table | | Module Controller | Include Controller | |---|---|---| | Reach | inside the loaded plan only | any `.jmx` on the filesystem | | Reference form | path of element names, picked from a tree | the *Filename* field, prefixed by `includecontroller.prefix` | | Variables in the reference | not applicable | **not supported** in the field | | Target visible while editing | yes — it is a node in your tree | no — contents are not displayed | | Broken reference | `JMeterStopTestException`, run shut down | logged, run **continues** with nothing included | | Cost per thread | none beyond the one-time splice | the file is re-read and re-parsed on every element clone | | Three plans, one copy | no — each plan needs its own fragment | yes | ## What the Include Controller costs at run time Both mechanisms substitute once, while `JMeter.pConvertSubTree()` builds the runnable tree, so neither adds work per iteration. But the Include Controller leaves a live element in the tree, and `IncludeController.clone()` calls `resolveReplacementSubTree()`, which re-reads and re-parses the named `.jmx`. JMeter clones the thread group subtree once per thread (`AbstractThreadGroup.cloneTree()`), and the source carries a standing `TODO` about exactly this — *"fix so that this is only called once per test, instead of at every clone"*. The practical consequence: a plan with an Include Controller parses that file once per thread as the threads are created, and the result is thrown away, because the tree was already spliced before any of them started. On a plan with a handful of threads it is invisible. On an injector ramping thousands, it is startup work and disk reads that nobody expects, and it argues for keeping included fragments small. ## Failure behaviour is the part teams get wrong These two mechanisms fail in opposite directions, and it is the single most useful thing to know when choosing: - **Module Controller.** An unresolved node path throws `JMeterStopTestException` with a message naming the controller and asking whether something on the path was renamed. The run stops. Loud, early, unmissable. - **Include Controller.** A file that cannot be opened, or one with no Test Fragment in it, is logged and the branch contributes nothing. The run proceeds and can pass. So the mechanism that is better at sharing is also the one that can leave three plans green while none of them logged in. If you take the Include Controller, take the obligation with it: something has to assert that the fragment's own sampler labels actually appear in the results. ## A shape that uses both For the login flow behind three plans: 1. `login.jmx` contains a **Test Fragment** named `Login Fragment` holding the login samplers and the extractor that captures the token. 2. The same file carries a debug **Thread Group** with a **Module Controller** pointing at that fragment, so the fragment can be run standalone. The include process ignores that Thread Group, which is why the manual suggests it. 3. Each of the three plans carries an **Include Controller** whose *Filename* is `login.jmx`, with `includecontroller.prefix` supplying the directory per machine. 4. Each Include Controller gets a **distinct Name**, per the manual's warning about including one file more than once. ## What neither of them gives you - **Parameters.** Neither controller passes arguments. The spliced elements read whatever variables the calling plan has already set; how those variables get set is a separate subject. - **Separate result labels.** The samplers spliced in are the fragment's own elements, so all three plans report the login steps under the same labels. That is often what you want; it is not always. - **Editability, in the Include Controller's case.** The tutorial page states the limitation directly: the contents are only loaded at run time, so users cannot view or edit an included module from the plan that includes it. Reviewing a change to the login flow means opening `login.jmx`, not the plan.

  • What does a broken reference cost you with each of JMeter's two reuse controllers?
    A Module Controller that cannot resolve its stored node path throws a stop-test exception naming the controller, so the run ends before sampling. An Include Controller that cannot load its file, or finds no Test Fragment in it, logs the problem and contributes an empty branch, so the plan runs and can finish green having skipped those steps entirely.
  • What does a JMeter Include Controller cost per thread at run time?
    A file read and an XML parse. Cloning the element re-resolves the included file, and JMeter clones the thread group subtree once per thread, so the .jmx is parsed once per thread as threads are created. The result is unused, because the substitution already happened while the test tree was built; the source carries a TODO acknowledging the repeated load.
  • Can you reuse one login flow across three JMeter plans with Module Controllers alone?
    Only by copying the fragment into all three plan files, because a Module Controller resolves names inside the plan tree it was loaded with and cannot name another .jmx. That gives you three copies to keep in step, which is the duplication the Include Controller exists to remove.

saying these in an interview costs you the question

  • Copies the login fragment into all three plan files
  • Says an Include Controller lets you edit the fragment in place
  • Treats a Module Controller as able to reach another .jmx
  • Assumes both controllers fail the same way when the target is missing
  • Expects a shared fragment to take parameters like a function