skip to content

A JMeter JSON Extractor with Match No. -1 found the order id, yet ${orderId} renders literally. Why?

level: seniorimportance: should knowfreq 46%

answer

  1. The failure signal is inverted here
  2. Indexed names appear, the plain name does not
  3. A negative setting changes which variables exist
  4. Check whether the suffixed variable was written

basics

~10 s

Match No. -1 makes the JSON Extractor write orderId_1, orderId_2 and orderId_matchNr, never the bare orderId. A literal ${orderId} downstream is the signal that extraction succeeded, not that it failed.

solid answer

~50 s

With a **negative Match No.** the JSON Extractor switches to extract-all mode: it writes `orderId_1`, `orderId_2` and so on, plus `orderId_matchNr` holding the count, and — only if **Compute concatenation var (suffix _ALL)** is ticked — `orderId_ALL`. It does **not** write the bare `orderId` on success. That name is written on the failure paths: an empty response, an expression that matched nothing, or an exception all store the Default Value under `orderId`. So the signal is inverted. An unresolved `${orderId}` means the capture worked and you are reading the wrong variable; a populated `${orderId}` under Match No. -1 means it did not. The fix is either Match No. `1` for a single expected value, or `${orderId_1}` downstream. Note this behaviour is not shared across JMeter's extractor family — the XPath2 Extractor sets the bare name to its first match whatever the Match No.

code

text · 13 lines
text
# Confirmation body returned by the sampler
{"status":"CONFIRMED","order":{"id":"ORD-88134","lines":[{"sku":"A1"},{"sku":"B7"}]}}

# JSON Extractor: name=orderId, expression=$.order.id, Match No.=-1, default=NO_ORDER_ID
# Variables after the sample, as a Debug Sampler would show them:
orderId_1=ORD-88134
orderId_matchNr=1
# orderId          -> never written; ${orderId} goes downstream as literal text
# orderId_ALL      -> absent, because Compute concatenation var is unticked

# Same element with Match No.=1:
orderId=ORD-88134
orderId_matchNr=1

go deeper

for a junior

Recall that the match number chooses which match is taken, and that a negative value is a distinct mode rather than a bigger positive one.

for a middle

Explain exactly which variables extract-all mode writes, which it does not, and the two conditions the concatenated variable needs before it appears.

for a senior

Show the diagnosis: check whether the indexed variable exists before touching the expression, and recognise that the failure signal here is inverted.

for a principal

Argue for stating the expectation in the plan. A match number of one declares that a single value is expected; extract-all leaves every downstream reference carrying a suffix forever.

Assumed version: Apache JMeter 6.0.0. ## What a negative Match No. actually writes The JSON Extractor has three distinct behaviours keyed off Match No., and the negative one is not simply "the positive one, plus extras": - **Positive N** — writes `orderId` with the Nth match, and `orderId_matchNr` with how many were found. If N exceeds the count, `orderId` gets the Default Value. - **Zero** — writes `orderId` with a randomly chosen match, and writes **no** `orderId_matchNr` at all on success. - **Negative** — writes `orderId_1` … `orderId_N` and `orderId_matchNr`, and leaves the bare `orderId` alone. The last case holds even when the expression matched exactly once. One match under Match No. `-1` gives you `orderId_1` and nothing else; the plain name is never assigned. ## The inverted signal, and why it wastes an afternoon JMeter leaves an unresolved variable reference as literal text, so the next request goes out asking for `/orders/${orderId}`. The instinct is that the extractor failed. Under Match No. `-1` the opposite is true: | What you see downstream | What it means under Match No. -1 | |---|---| | Literal `${orderId}` | The extraction **worked**; you are reading a name it never writes | | `NO_ORDER_ID` (your default) | The extraction **failed** — nothing matched, or the body was empty | | The real id | You are not on Match No. -1, or you read `${orderId_1}` | So the first diagnostic move is not to go looking at the expression. It is to add a Debug Sampler or open the sample's variables and see whether `orderId_1` and `orderId_matchNr` exist. If they do, the expression is fine and only the reference is wrong. ## Where _ALL comes from, and when it does not `orderId_ALL` is the concatenation of every match joined with a comma. Two conditions must both hold before it appears: 1. **Compute concatenation var (suffix _ALL)** must be ticked. It is **off** by default. 2. Match No. must be **negative**. With a positive or zero Match No. the checkbox does nothing. There is a stale-state wrinkle here too. Between iterations the element clears `orderId_matchNr` and the indexed `orderId_1…N` variables, but it does not clear `orderId_ALL`. A thread whose second iteration finds nothing can therefore still be carrying the first iteration's `_ALL` value. ## The family does not agree on this This is element-specific behaviour, not a JMeter-wide rule, which is exactly why it catches people who have used a different extractor before: | Element | Bare reference variable under a negative Match No. | |---|---| | JSON Extractor | Not written on success; written with the default only on a miss | | JSON JMESPath Extractor | Same — only the indexed names and `_matchNr` are written | | XPath2 Extractor | Seeded with the default, then overwritten with the **first** match | | XPath Extractor | Same as XPath2 — the bare name always ends up set | | CSS Selector Extractor | Left at the default, which it only writes if you supplied one | Someone whose habits came from the XPath2 Extractor will reasonably assume the bare name is always populated, because on that element it is. ## Fixing it, and choosing deliberately Two fixes, and they are not equivalent: 1. **Set Match No. to `1`.** Correct when the confirmation body carries exactly one id. It states the expectation, populates `${orderId}`, and a second unexpected match is quietly ignored rather than changing which variables exist. 2. **Keep `-1` and read `${orderId_1}`.** Correct when you genuinely want every match — the line items of an order, say — and intend to iterate over them by index using `${orderId_matchNr}` as the bound. Reaching for `-1` when you want one value is the actual defect. It is a habit picked up from tutorials that use `-1` to "see everything" while debugging, and then never gets turned back down. Leave a plan on `-1` and every downstream reference has to remember the suffix, forever.

  • Under Match No. -1, what would a populated ${orderId} tell you?
    That the extraction failed. The bare name is written only on the miss paths — an empty response, an expression that matched nothing, or an exception — and what it holds is the Default Value. Under a negative Match No. a populated bare variable is a failure report, which is why an obviously wrong default such as `NO_ORDER_ID` earns its place.
  • You tick Compute concatenation var but nothing named _ALL appears. What is wrong?
    The Match No. is almost certainly not negative. The checkbox only takes effect in extract-all mode; with a positive or zero Match No. it is inert. Worth knowing too that the element clears the indexed variables and the matchNr between iterations but never clears an existing `_ALL`, so a stale one can outlive the run that produced it.
  • How would you iterate over every line item captured with Match No. -1?
    Read `${orderId_matchNr}` for the count and index the numbered variables from 1 up to it. The element guarantees the numbering is contiguous from 1 and clears leftovers from a previous iteration, so the count and the variables stay consistent within a thread.

saying these in an interview costs you the question

  • Reads a literal variable reference as proof the extraction failed
  • Believes a negative Match No. just adds variables to the positive case
  • Expects the concatenated variable to appear without ticking its checkbox
  • Assumes every JMeter extractor writes the bare name the same way
  • Leaves a plan on extract-all after debugging and never turns it back