skip to content

A JMeter checkout run passed every sample although each request carried a literal ${sessionId}. What happened?

level: seniorimportance: should knowfreq 46%

answer

  1. Unset does not mean empty
  2. The reference travels as its own text
  3. Servers answer it politely
  4. The status range absorbs the mistake

basics

~20 s

An unset JMeter variable renders as the literal text ${sessionId} rather than as an empty string. The server answered those requests inside the 200 to 399 range, and with no assertion attached JMeter recorded every one as a success.

solid answer

~40 s

A `${name}` reference that is not a function call compiles to a `SimpleVariable`, whose `toString()` looks the name up in the thread's variables and returns `"${" + name + "}"` whenever the lookup comes back null. So an unset variable is not blank — the reference characters themselves go on the wire, in the header, the URL or the body. Servers rarely answer that with a `5xx`: they return a `200` login page, a `200` "invalid session" JSON document, or a `302` to `/login`. All three sit inside JMeter's 200–399 success range, so every sample is green and the run's error count stays at zero. The literal text is visible only in the *Request* tab of a View Results Tree, or to an assertion you added.

code

http · 4 lines
http
GET /api/orders?page=1 HTTP/1.1
Host: shop.example.com
Authorization: Bearer ${sessionId}
Accept: application/json

go deeper

for a junior

Remember that a JMeter variable you never set is not blank: the text ${name} itself is sent. Look at the Request tab of a results tree rather than assuming substitution worked.

for a middle

Explain SimpleVariable's toString: it returns the reference text when the lookup returns null, which is why the request is well formed and wrong rather than visibly broken.

for a senior

Diagnose the run end to end — unresolved reference, polite 2xx or 3xx reply, status-only success rule — and say what the run's numbers actually measured before you quote any of them.

for a principal

Treat a run that cannot distinguish a served request from a rejected one as producing no evidence, and decide what a JMeter plan must assert before the numbers it produces are allowed into a report.

This is the JMeter failure that produces the most confident wrong report: a full run, thousands of samples, zero errors, and not one request that the application could actually serve. ## Unset does not mean empty When JMeter compiles a field containing `${sessionId}`, a reference that is not a function call becomes a `SimpleVariable`. Its `toString()` is short enough to quote in full in an interview: ```java String ret = null; JMeterVariables vars = getVariables(); if (vars != null) { ret = vars.get(name); } if (ret == null) { return "${" + name + "}"; } return ret; ``` A missing variable therefore renders as its own reference text. There is no warning, no error-level log line, and nothing that changes the sample's verdict — the substitution simply produces a different string from the one you intended. That is worth contrasting with a **malformed function call**, which behaves in exactly the opposite way: a wrong parameter count aborts the test-tree compile with `Error occurred compiling the tree:` before any thread starts, so you get no results file at all. Functions fail loudly; plain variable references fail silently. ## Why the server keeps the run green The requests that go out are syntactically valid. A header reading `Authorization: Bearer ${sessionId}` is a well-formed header with an odd value; a URL containing `/orders/${sessionId}` is a well-formed URL. Real applications answer these politely: - an API returns `200` with `{"error":"invalid session"}` - a web front end returns `200` with the login page - an edge tier returns `302` to `/login` - a strict validator returns `400`, and this is the only one of the four JMeter fails on its own Three of those four are inside `200–399`, so the samples pass. This is the same run that **reported zero errors while serving error pages** — the elapsed times JMeter recorded are the times of rejections, and the sample counts in its report are counts of rejections. ## How the variable came to be unset Two causes cover nearly all cases, and both are silent by design: 1. **The extractor missed.** The reply that should have carried the session id was itself an error page, so nothing matched, and with a blank Default Value and the empty-default box unticked the extractor wrote nothing at all. 2. **The names do not agree.** The reference name on the extractor and the `${...}` in the sampler differ by a letter or by case. Nothing in JMeter cross-checks them. Both leave the reference unresolved and both leave the verdict untouched. ## Confirming it in five minutes The diagnosis is quick once you know what you are looking at: - Open a **View Results Tree** and select a passing sample. The **Request** tab shows the request body and the headers JMeter added, with `${sessionId}` still sitting there in plain sight. The green icon beside it is telling you only that the reply's status was in range. - Put a **Debug Sampler** after the extractor with *JMeter variables* ticked. Its response data lists the thread's variables; `sessionId` is either missing from the list or holding an extractor default. - Read the **Response data** tab of the sample that was supposed to supply the value. That is usually where the real story is: it was an error page all along. ## The fix, and the guard Correct the extraction, then make the same mistake impossible to ship unnoticed. Give the extractor a distinctive `Default Value` such as `NOT_FOUND`, and attach a Response Assertion to the sampler that consumes it — either matching something only a genuine reply contains, or matching the marker so the plan fails loudly when the extraction missed. A plan that sends a value it did not capture should be a red sample, and the only way to get that in JMeter is to say so with an assertion.

  • Does JMeter log a warning when a ${name} reference cannot be resolved?
    No. An unresolved plain variable is not an error condition — SimpleVariable simply returns the reference text. Only a malformed function call is loud, and that one aborts the test-tree compile with `Error occurred compiling the tree:` before any thread starts, so it produces no results file rather than a degraded one.
  • Where in JMeter do you see that the literal reference was sent rather than a value?
    In a View Results Tree, the Request tab shows the body and the headers JMeter added, with the reference still in place. A Debug Sampler with JMeter variables ticked shows the same problem from the other side: the variable is absent from the list, or holds an extractor's default value.
  • Would a Response Assertion on the consuming sampler have caught this?
    Yes, provided it tests something the error reply cannot satisfy. A Field to Test of Text Response with a pattern such as an `orderId` key fails on the invalid-session document, and one on Response Code pinned to 200 would not, because the error reply also returned 200.

saying these in an interview costs you the question

  • Says an unset variable resolves to an empty string
  • Expects JMeter to warn on an unresolved reference
  • Assumes the request would have failed with a 500
  • Believes the results file flags unresolved references
  • Thinks a null value stops the thread