skip to content

JMX File Format

A plan is a machine-written XML file, which is why review and merging hurt. Interviewers ask because the honest answer is smaller plans and parameters, not a better diff tool.

on this pageshow

explore

questions

6

In a JMeter .jmx file, why does every test element have a hashTree element after it?

level: juniorimportance: must knowfreq 55%

answer

  1. The XML is flatter than the tree looks
  2. Children are not inside the element tag
  3. Two nodes per element, always
  4. Even a leaf gets an empty tag

basics

~10 s

A .jmx writes JMeter's tree as alternating pairs: the element, then a hashTree holding that element's children. Nesting lives in the paired hashTree, so an element with no children still gets an empty one.

solid answer

~40 s

A JMeter plan is a `ListedHashTree`, and the `.jmx` serialises it as a flat run of alternating pairs: an element node, then the `<hashTree>` that carries that element's children. A Thread Group is therefore **not** the XML parent of its samplers — the `<hashTree>` written immediately after it is. An element with no children still gets an empty `<hashTree/>`, so the alternation is never broken. On load, JMeter's `HashTreeConverter` walks the children of a `<hashTree>` and simply toggles: first child is a key, second is that key's subtree, third is a key again. Sibling order inside a `<hashTree>` is preserved, because `ListedHashTree` keeps insertion order. That toggling is why the file is unforgiving of hand edits — remove or duplicate one `<hashTree>` in the middle and every later pair shifts by one.

code

xml · 9 lines
xml
<hashTree>
  <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Checkout" enabled="true">
    <stringProp name="ThreadGroup.num_threads">50</stringProp>
  </ThreadGroup>
  <hashTree>
    <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /cart" enabled="true"/>
    <hashTree/>
  </hashTree>
</hashTree>

go deeper

for a junior

Recall that a .jmx alternates element and hashTree, and that the hashTree after an element holds its children rather than the element tag containing them.

for a middle

Explain that the tag maps to ListedHashTree and that the loader toggles key/subtree on each child, so an odd number of children shifts everything after it.

for a senior

Show you can spot the damage in a real file: count the children of each hashTree, and prefer re-applying a change in the GUI over hand-repairing pairs.

for a principal

Own the consequence for the team — the format makes textual merges of a plan unreliable, so decide how plans are owned, split and reviewed rather than buying a better diff tool.

A `.jmx` file looks like an XML tree of test elements, and it is not. It is a **flat, strictly alternating list of key/subtree pairs**, and understanding that one fact explains most of what the format does to you. ## The shape on disk The root is `<jmeterTestPlan>`, and immediately under it is a single `<hashTree>`. Inside any `<hashTree>`, children alternate: element, its `hashTree`, element, its `hashTree`, and so on. ```xml <jmeterTestPlan version="1.2" properties="5.0" jmeter="6.0.0"> <hashTree> <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> <boolProp name="TestPlan.functional_mode">false</boolProp> </TestPlan> <hashTree> <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Checkout" enabled="true"> <stringProp name="ThreadGroup.num_threads">50</stringProp> </ThreadGroup> <hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /cart" enabled="true"/> <hashTree/> </hashTree> </hashTree> </hashTree> </jmeterTestPlan> ``` Read that as three pairs, not as four levels of nesting. `TestPlan` is followed by the `hashTree` that owns it; the Thread Group is followed by the `hashTree` that owns *its* children; the sampler has no children, so it gets the empty `<hashTree/>` that keeps the alternation intact. ## Why pairs instead of nesting The alias table in `bin/saveservice.properties` maps the tag `hashTree` to `org.apache.jorphan.collections.ListedHashTree`. That class is a map from each node to the subtree beneath it, with a separate list that preserves the order keys were added. Serialising a map of key to subtree naturally produces two nodes per entry, and `HashTreeConverter.marshal` does exactly that: for every item in the tree it writes the item, then writes the tree hanging off it. Loading is the mirror image. `HashTreeConverter.unmarshal` keeps a boolean and flips it on every child: read a child as a key, add it, read the next child as that key's subtree, set it, flip back. | What you see in the GUI | What the file holds | |---|---| | A sampler nested under a Thread Group | Thread Group element, then a `hashTree` whose first child is the sampler | | A leaf element with nothing under it | The element, then `<hashTree/>` | | Dragging an element up one position | Both the element node **and** its paired `hashTree` move together | | Sibling order in the tree | Order of the pairs inside the enclosing `hashTree` | ## What breaks the alternation Because the reader only toggles, it has no way to notice that a pair is malformed — it just carries on one position out of step. The damage patterns worth recognising: 1. **A `<hashTree>` deleted from the middle.** The next element node is handed to the code that sets a subtree, which casts it to a `HashTree`. That throws, and `SaveService` turns it into an `IllegalArgumentException` whose message starts `Problem loading XML from:` and names the file. 2. **A pair duplicated.** A conflict resolved by keeping both sides can leave an element with two `hashTree` nodes after it; everything following shifts. 3. **A pair pasted at the wrong depth.** The file still loads, but the element now lives under a different parent, which silently changes what it applies to. 4. **A trailing `<hashTree/>` dropped at the very end of a block.** This one is tolerated — the last element is simply added with an empty subtree — which is why the failure feels arbitrary. Note that JMeter loads a plan as a unit. There is no partial load: either the whole file parses or you get nothing. ## What this costs a team Two engineers editing one plan on the same day feel this immediately. One reorders a controller; the other adds a sampler three lines below it. Both changes are legitimate, both touch element/`hashTree` pairs, and a textual merge that interleaves them can easily leave an odd number of children inside one `hashTree`. The merged file is still well-formed XML — a diff tool will not complain — and it fails only when someone tries to open it. The practical rule that follows: never repair a `.jmx` by hand-merging pairs. Take one side whole, open it, and re-apply the other side's change in the GUI, which regenerates a correctly paired file.

  • If a hashTree is missing from the middle of a JMeter .jmx, does JMeter load the part before the damage?
    No. JMeter parses the plan as a single unit through SaveService.loadTree; when the pairing shifts, the converter fails and the load is rethrown as an IllegalArgumentException whose message begins 'Problem loading XML from:'. You get no plan at all, not a partial one.
  • In a JMeter .jmx, what decides the order of sibling elements inside one hashTree?
    The order of the pairs in the file. The hashTree tag maps to ListedHashTree, which keeps a separate list of keys in the order they were added, so the file's pair order is the tree order you see in the GUI.

Like a packing list that writes each box's label and then, right after it, the carton holding that box's contents — an empty carton still gets written, so the label-carton rhythm never breaks.

saying these in an interview costs you the question

  • Says the sampler is nested inside the Thread Group tag
  • Thinks an empty hashTree tag means the element is disabled
  • Claims JMeter loads a damaged .jmx up to the broken point
  • Believes hashTree is an optional wrapper you can omit
  • Assumes moving an element in the file moves only one node
open as a page

In a JMeter .jmx, what do an element's guiclass, testclass, testname and enabled attributes mean?

level: juniorimportance: should knowfreq 40%

basics

~20 s

They are four JMeter element properties written as XML attributes: the editor panel class, the runtime element class, the display name shown in the tree, and whether the element and its subtree run at all.

open as a page

In a JMeter .jmx, what do stringProp, boolProp, elementProp and collectionProp each hold?

level: middleimportance: should knowfreq 34%

basics

~10 s

They are JMeter's property tags: stringProp a text value, boolProp a true or false value, elementProp a whole nested test element, and collectionProp an ordered list of properties.

open as a page

A merged JMeter .jmx no longer opens after two engineers edited it the same day. How do you diagnose it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Separate the two failure modes first: XML that is no longer well formed, or well formed XML whose element and hashTree pairing broke. Confirm each parent version opens, then re-apply the smaller change in the GUI.

open as a page

Your team reviews JMeter .jmx changes by reading the diff. What does that miss, and what would you require instead?

level: principalimportance: should knowfreq 38%

basics

~20 s

A diff shows changed lines, not changed behaviour. One attribute can remove a whole branch, a moved element rewrites many lines and changes nothing, and property keys carry no context. Require the plan to be opened and run.

open as a page

On a JMeter .jmx root element, what do the version, properties and jmeter attributes mean?

level: middleimportance: nice to knowfreq 18%

basics

~10 s

They record the JMX format version, the version of JMeter's save-alias file, and the JMeter build that wrote the file. Only the first is read back, and it barely changes anything.

open as a page