In a JMeter .jmx file, why does every test element have a hashTree element after it?
answer
- The XML is flatter than the tree looks
- Children are not inside the element tag
- Two nodes per element, always
- Even a leaf gets an empty tag
basics
~10 sA .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 sA 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<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
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.
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.
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.
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