In Gatling's Expression Language, which forms turn an absent or null Session attribute into a value instead of failing the request, and what does each one produce?
answer
- Expression Language has no null
- A missing attribute fails the request
- Exists and isUndefined never fail
- They swallow any failure underneath
- jsonStringify rescues null, not absence
basics
~10 sA bare placeholder on a missing attribute fails, so Gatling never builds the request. #{x.exists()} and #{x.isUndefined()} return booleans instead of failing, and #{x.jsonStringify()} rescues a null value but not an absent one.
solid answer
~50 sA bare `#{coupon}` on an attribute the Session does not hold is a resolution **failure**, not an empty string: Gatling cannot build the request, logs `Failed to build request <name>: No attribute named 'coupon' is defined`, writes one entry into the run's Errors table, and lets the virtual user continue with its Session marked as failed. No request record is written at all: the attempt is not counted as a request, not counted as a KO, has no response time, and gets no request line in the report. Three forms behave differently. `#{coupon.exists()}` resolves to `false` and `#{coupon.isUndefined()}` to `true` — neither ever fails, because both swallow whatever went wrong underneath and report a boolean. `#{coupon.jsonStringify()}` renders a JSON-safe value: a String gains quotes, a number stays bare, and an attribute whose stored value is `null` becomes the literal `null` — but a **missing** attribute still fails.
code
java · 5 linesexec(http("Checkout")
.post("/accounts/#{accountId}/orders")
.queryParam("hasCoupon", "#{coupon.exists()}")
.body(StringBody("{\"note\": #{note.jsonStringify()}}"))
.asJson());go deeper
Be ready to say that a placeholder whose attribute is missing fails rather than rendering blank, and to name the two forms that ask about presence instead.
Be ready to explain that exists() reports on the whole access rather than just the attribute name, including a present-but-null value, and what jsonStringify() emits for each value type.
Be ready to read a run whose error rate looks clean while a slice of its users never sent a request at all, find those attempts in the Errors table, and say which scenario change removes them.
Be ready to argue how a suite should treat optional data so that its numbers stay a measurement of the system rather than of the script — including the requests a build failure removes from the count without ever reaching the error rate.
Gatling's Expression Language has no `null`. Every resolution either produces a value or produces a **failure**, and knowing which forms can fail is what separates a scenario that survives optional data from one that quietly stops sending part of its traffic. ## What a bare placeholder does when it cannot resolve Suppose a scenario applies a coupon, but only some virtual users have one. A request built from `#{coupon}` for a user without the attribute does not send an empty parameter. The expression fails, and because Gatling could not build the request: 1. It logs `Failed to build request <request name>: No attribute named 'coupon' is defined`. 2. It books the attempt as an **Error load-event**, which surfaces as one row in the run's Errors table — in the live console summary and in the HTML report's global Errors table. No request record is written, so nothing is added to the request count, the KO count, the response-time statistics or the per-second charts. 3. It marks the virtual user's Session as failed and lets the user carry on to the next action. The same happens for an index past the end of a list, a key a map does not hold, or a `size()` call on a value that is not a collection — the message differs, the outcome does not. On a report this is the confusing case: the Errors table grows rows whose text names the request and the unresolved attribute and mentions no HTTP status at all, while the request counts, the KO count and the per-second charts stay exactly as if the user had never reached the action. One trace does leak out elsewhere: because the Session is marked as failed, every enclosing `GroupBlock` is flipped to KO, so a crashed request inside a `group(...)` still produces a KO **group** record — a group-level signal, not a request or a KO count. ## The three forms that do not propagate a failure | form | attribute missing | attribute holds `null` | attribute holds a value | |---|---|---|---| | `#{coupon}` | fails | fails | the value | | `#{coupon.exists()}` | `false` | `false` | `true` | | `#{coupon.isUndefined()}` | `true` | `true` | `false` | | `#{coupon.jsonStringify()}` | fails | the literal `null` | a JSON-encoded value | `exists()` and `isUndefined()` are exact inverses and neither can fail. They are broader than their names suggest: each reports on **any** failure underneath it, not just on a missing key. So `#{ids(9).exists()}` is `false` when the list has three elements, and — the one that surprises people — an attribute that is present but holds `null` also reports `false`, because reading a null attribute is itself a failure. The honest reading of `exists()` is *can this expression produce a value?*, not *is this key in the map?* That makes it a dependable guard, and it also means it cannot tell you *why* a value was unavailable. `jsonStringify()` is a different tool. Its job is to make a value safe to paste into a JSON body: * a String is wrapped in double quotes and escaped, * a number or a boolean is emitted bare, * a list becomes a JSON array and a map, POJO, record or case class becomes a JSON object, * an attribute whose stored value is `null` becomes the unquoted literal `null`. That last row is the one worth holding on to: **`jsonStringify()` rescues a null value, not a missing attribute.** If the attribute is not in the Session at all, the expression fails exactly as a bare placeholder would. It is null-tolerant, not absence-tolerant. ## Using them ```java exec(http("Checkout") .post("/accounts/#{accountId}/orders") .queryParam("hasCoupon", "#{coupon.exists()}") .body(StringBody("{\"note\": #{note.jsonStringify()}}")) .asJson()); ``` Note what `jsonStringify()` buys in that body. Written as `"note": "#{note}"` the quotes are hard-coded and the bare placeholder is used, so a stored `null` fails the expression and the request is never sent, while a value containing a double quote produces invalid JSON. Written as `"note": #{note.jsonStringify()}` the quoting is decided by the value's type, the escaping is done for you, and a stored `null` comes out as the JSON literal. ## Choosing a strategy for optional data Three approaches, in increasing order of effort: * **Guard with `exists()` / `isUndefined()`** when the optionality is genuine and the scenario should branch on it. This is the cheapest and it keeps everything in EL. * **Give the attribute a value early**, with a function that sets a default before the request that needs it. This keeps every downstream expression a plain placeholder and is the easiest to read. * **Drop to a function** for the parameter itself when the fallback needs logic — a different default per user segment, say. The function reads the Session through its API, where absence is an ordinary `null` or an `Option` rather than a failure. What all three have in common is that they make the absence explicit. The failure mode worth avoiding is the accidental one: a scenario in which Gatling silently fails to build requests for users that were never going to have the data. The trap here is the inverse of what most people expect. These failures never reach the summary count at all — they are not counted as requests and not counted as KOs, so the success rate, the per-request KO column and any `global.failedRequests` or `global.successfulRequests` assertion are blind to them. The danger is not that they corrupt the error rate; it is that a scenario can report a perfect error rate while a slice of its users never sent a request.
- What does `#{ids(9).exists()}` resolve to when `ids` holds three elements?`false`. The form reports `false` for any failure underneath it, not only for a missing attribute, so an out-of-range index, a missing map key, a wrong-typed value and an attribute holding `null` all come back as `false`. That makes it a dependable guard, but it also means it cannot distinguish absence from any other resolution problem.
- Why prefer `"note": #{note.jsonStringify()}` over `"note": "#{note}"` in a JSON body?Because the second hard-codes the quoting. A numeric value comes out quoted when it should not be, an embedded double quote breaks the document, and a stored `null` fails the bare placeholder outright so the request is never sent. `jsonStringify()` picks the encoding from the value's type, escapes the content, and emits a bare `null` for a null value.
saying these in an interview costs you the question
- Expecting a missing attribute to render as an empty string
- Thinking an unresolved placeholder still sends the request
- Expecting a build failure to show up in the run's KO count or error rate
- Believing jsonStringify rescues an absent attribute, not just a null one
- Assuming exists() is true for an attribute that is present but holds null