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?
answer
- Some placeholders invent the value
- Timestamps, dates, randoms, alphanumerics
- Bounded ranges exclude the upper bound
- Every occurrence is evaluated separately
- Two UUID functions, fast versus secure
basics
~20 sGatling 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 sBesides 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 linesexec(http("Create order")
.post("/accounts/#{accountId}/orders/#{randomUuid()}")
.body(StringBody("{\"placedAt\": \"#{currentDate(yyyy-MM-dd'T'HH:mm:ss)}\"}"))
.asJson());go deeper
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.
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.
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.
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