skip to content

Which values can a Gatling Expression Language string produce on its own with no Session attribute behind it, and how many values does `#{randomUuid()}` yield when it appears twice in one URL?

level: middleimportance: must knowfreq 52%

answer

  1. Some placeholders invent the value
  2. Timestamps, dates, randoms, alphanumerics
  3. Bounded ranges exclude the upper bound
  4. Every occurrence is evaluated separately
  5. Two UUID functions, fast versus secure

basics

~20 s

Gatling EL ships generator functions that need no Session attribute: currentTimeMillis(), currentDate(pattern), randomUuid(), randomSecureUuid(), randomInt(), randomLong(), randomDouble() and randomAlphanumeric(). Each occurrence is evaluated separately, so two randomUuid() calls in one URL give two different ids.

solid answer

~30 s

Besides reading Session attributes, Gatling's Expression Language has built-in **generator functions** that manufacture a value on the spot: `#{currentTimeMillis()}`, `#{currentDate(<pattern>)}`, `#{randomUuid()}`, `#{randomSecureUuid()}`, `#{randomInt()}`, `#{randomInt(min,max)}`, `#{randomLong()}`, `#{randomLong(min,max)}`, `#{randomDouble(min,max)}`, `#{randomDouble(min,max,digits)}` and `#{randomAlphanumeric(length)}`. They mix freely with attribute placeholders, so `"/accounts/#{accountId}/orders/#{randomUuid()}"` combines an account id an earlier step saved with a fresh identifier. Each occurrence is a separate part of the compiled expression and is evaluated independently, so two `#{randomUuid()}` in one URL produce two different UUIDs, and a retried request gets a new one. The bounded forms are half-open: `#{randomInt(5,10)}` yields 5 through 9.

code

java · 4 lines
java
exec(http("Create order")
  .post("/accounts/#{accountId}/orders/#{randomUuid()}")
  .body(StringBody("{\"placedAt\": \"#{currentDate(yyyy-MM-dd'T'HH:mm:ss)}\"}"))
  .asJson());

go deeper

for a junior

Be ready to name a couple of the generator functions and to write a request path that mixes a saved attribute with a freshly generated identifier.

for a middle

Be ready to explain that each occurrence is evaluated independently, that bounded ranges exclude the upper bound, and that a bad range fails while the simulation is built.

for a senior

Be ready to spot the idempotency-key bug a generator placeholder creates under retries, and to say how you would pin one generated value across several requests.

for a principal

Be ready to draw the line between values a test may invent inline and data that has to come from a governed source, and to say what pushes a value across it.

Most Gatling placeholders read something the virtual user already carries. A second family manufactures a value instead, which is how a simulation gets a unique identifier, a timestamp or a random number without a feeder file and without dropping out of the Expression Language. ## The generators that ship in the 3.15.x line | expression | produces | |---|---| | `#{currentTimeMillis()}` | the current epoch milliseconds | | `#{currentDate(<pattern>)}` | now, formatted with a `java.time` `DateTimeFormatter` pattern | | `#{randomUuid()}` | a random UUID string, built fast from a thread-local generator | | `#{randomSecureUuid()}` | a UUID from the JDK's own `UUID.randomUUID()` — slower, cryptographically secure | | `#{randomInt()}` / `#{randomInt(min,max)}` | a random `Int`, full range or bounded | | `#{randomLong()}` / `#{randomLong(min,max)}` | a random `Long`, full range or bounded | | `#{randomDouble(min,max)}` / `#{randomDouble(min,max,digits)}` | a random `Double`, optionally capped to *n* decimal places | | `#{randomAlphanumeric(length)}` | a random alphanumeric string of that length | Three details about the bounded forms are worth memorising: * **The range is half-open.** `#{randomInt(5,10)}` returns 5, 6, 7, 8 or 9 — never 10. * **Negative bounds are legal**, so `#{randomInt(-10,-5)}` is a valid expression. * **An inverted range fails when the expression is compiled**, not during the run. `#{randomInt(20,1)}` raises an `ElParserException` while the simulation is being built, so the run never starts. The `randomDouble` forms are equally strict about their literals: each bound must be written as digits, a dot, digits — `0.42` is accepted, `.42` and `2.` are not. ## Each occurrence is evaluated on its own An EL string is compiled once, into a list of parts, and then evaluated per use. A generator part calls its generator every single time it is evaluated. The consequences are the ones people get wrong: 1. **Two occurrences in one string disagree.** `"/orders/#{randomUuid()}?trace=#{randomUuid()}"` sends two different UUIDs. If the path segment and the trace header must match, generate the value once — save it into the Session with a function — and read it back with an attribute placeholder in both places. 2. **A retry is a new value.** A request re-executed inside a retry block resolves its expression again, so an idempotency key written as `#{randomUuid()}` changes on every attempt, which defeats the point of an idempotency key. 3. **Nothing is memoised across a scenario.** `#{currentTimeMillis()}` at step one and step ten give different numbers, which is usually what you want and occasionally not. ## The date pattern is a `java.time` pattern, with one parser limit `#{currentDate(<pattern>)}` formats `ZonedDateTime.now()` with a `java.time.format.DateTimeFormatter` pattern, so the letters are the JDK's: `yyyy-MM-dd`, `HH:mm:ss`, `'T'` for a literal T. Two practical points: * **The pattern is read straight out of the expression, so it may not contain `#`, `{`, `}`, `(` or `)`.** Those characters are what delimits the expression itself. Everything else, dots and quoted literals included, is fine. * **The value is produced when the request is built**, not when the simulation starts, so a long-running scenario sees the clock advance between its steps. ## Combining a saved value with a generated one The everyday shape is one attribute the scenario captured earlier plus one value invented now: ```java exec(http("Create order") .post("/accounts/#{accountId}/orders/#{randomUuid()}") .body(StringBody("{\"placedAt\": \"#{currentDate(yyyy-MM-dd'T'HH:mm:ss)}\"}")) .asJson()); ``` `accountId` comes out of the Session — it was saved by something earlier in the scenario. `#{randomUuid()}` and `#{currentDate(...)}` are produced on the spot. The two kinds of placeholder are written the same way and can sit side by side in one string. ## Choosing between the two UUID functions `randomSecureUuid()` delegates to the JDK's `java.util.UUID.randomUUID()`, the standard RFC 4122 version-4 generator. `randomUuid()` assembles the string itself from a thread-local random source; Gatling documents it as *fast but cryptographically insecure*. Under load that difference is real — the secure generator draws from the system entropy source and is measurably slower at high arrival rates. The rule of thumb: * **Use `randomUuid()`** for values the system under test only needs to see as distinct — a correlation id, a cache-busting suffix, an order reference the test itself invents. * **Use `randomSecureUuid()`** when the value must be unguessable, or when something downstream validates the UUID's version and variant bits. ## Where the generators stop They cover timestamps, numbers and opaque identifiers. They do not build a value that has to look like production data — a plausible name, a valid postcode, a card number that passes a checksum. Nor do they express any conditional logic. When either is needed, the value belongs in a function or in feeder data, not in an expression string.

  • A request needs the same generated id in both its path and a header. How do you do that?
    Generate it once and store it. Use a function that puts the value into the Session — for example `exec(session -> session.set("orderId", UUID.randomUUID().toString()))` in the Java API — then read it back as `#{orderId}` in both places. Two `#{randomUuid()}` occurrences would resolve independently and disagree.
  • What does `#{randomInt(5,10)}` return, and what happens if the bounds are reversed?
    It returns 5, 6, 7, 8 or 9: the lower bound is inclusive and the upper bound exclusive. Reversing them to `#{randomInt(10,5)}` is not a run-time failure — the expression is rejected with an `ElParserException` while it is being compiled, so the simulation never starts.

saying these in an interview costs you the question

  • Expecting two randomUuid() occurrences in one string to agree
  • Using randomUuid() as an idempotency key across retries
  • Reading randomInt(5,10) as inclusive of 10
  • Assuming a bad range fails mid-run rather than at build time
  • Reaching for a random-string-with-charset function that 3.15.x does not ship