In Gatling's Java DSL, what does exec(session -> ...) let you do, and what must never go inside that function?
answer
- A step that is just a function
- Session in, session out
- Runs on Gatling's shared engine threads
- Never block, never build actions there
basics
~20 sIt inserts a step that receives the virtual user's session and returns one, so you can set attributes between actions. It runs on Gatling's shared threads, so never block inside it, and SDK builders built there have no effect.
solid answer
~40 sBesides chains and actions, `exec` accepts a function from `Session` to `Session`. It becomes a step in the chain like any other, and whatever session it returns is what the next step sees — so `exec(session -> session.set("tier", "gold"))` writes an attribute, and returning the received session unchanged is a legitimate read-only step. Two hard limits apply. First, these functions run on Gatling's shared engine threads, so any long blocking operation — a remote API call, a database round trip, even console printing under load — stalls the generator. Second, Gatling SDK components are mere definitions, so building an `http(...)` request inside the function produces a dangling object that never executes.
code
java · 19 linesimport io.gatling.javaapi.core.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
class SessionFunctionExample {
{
// good: a fast, in-memory session update
ChainBuilder tagUser = exec(session -> session.set("tier", "gold"));
// bad: the request is a dangling definition and never runs
ChainBuilder broken = exec(session -> {
http("Cart").get("/cart");
return session;
});
ScenarioBuilder shopper = scenario("Shopper").exec(tagUser);
}
}go deeper
Be ready to recall that exec also accepts a function taking the session and returning a session.
Be ready to explain both constraints: no blocking work, and SDK components built inside the function are dangling definitions.
Be ready to connect the no-blocking rule to Gatling's non-blocking engine, and to describe what a stalled shared thread does to reported response times.
Be ready to rule on where custom logic may live in a team's simulations, so that scenarios stay declarative and reportable.
`exec` has an overload that takes a function rather than an action: in the Java API, a `Function<Session, Session>`. It is the escape hatch for everything the declarative DSL does not express — deriving one attribute from another, normalising a value captured earlier, or simply looking at the session while you debug. ## The contract The function receives the virtual user's current `Session` and must return a `Session`. Because `Session` is itself immutable, `set` does not modify anything in place — it returns a new session that you have to return: ```java exec(session -> session.set("tier", "gold")) ``` Returning the session you were given is perfectly valid and means "change nothing": ```java exec(session -> { System.out.println(session); // debugging only return session; }); ``` The step takes its place in the chain exactly like a request would, and composes exactly like one — you can store the result in a `ChainBuilder`, put it in a group, or attach it to a scenario. ## Rule one: never block Gatling's engine is asynchronous and non-blocking, and it does **not** dedicate a thread to each virtual user. The functions you hand to `exec` are executed on Gatling's shared threads. Anything slow in there is not slow for one user — it is slow for the engine: - No remote API calls, no HTTP client of your own, no message-broker round trips. - No JDBC queries or file reads on the hot path. - No console printing under load. Gatling's own documentation is blunt about this: standard output is a slow blocking output, and writing to it massively will freeze the engine. A `println` is a debugging tool for a one-user run, not something you leave in. The practical consequence is that a session function should be arithmetic, string work and map access — microseconds, not milliseconds. ## Rule two: no SDK components inside This is the subtler trap, and Gatling's documentation calls it out with a dedicated "bad" example: ```java exec(session -> { // dangling definition — produces no effect whatsoever http("Cart").get("/cart"); return session; }); ``` The DSL builds **definitions**. A definition only becomes an effect when it is chained into a structure that reaches `setUp`. Inside a function, at run time, there is nothing to chain it into: the object is constructed, ignored and garbage collected. No request is sent, no sample is recorded, and nothing fails — which is why people lose an afternoon to it. If you need a request to depend on the session, you do not build the request inside a function; you use the session in the request's own parameters, for example through Gatling's expression language or through a function passed to the request builder. ## The spelling in each SDK | SDK | form | |---|---| | Java | `exec(session -> session.set("foo", "bar"))` | | Kotlin | `exec { session -> session.set("foo", "bar") }` | | Scala | `exec { session => session.set("foo", "bar") }` | | JavaScript / TypeScript | `exec((session) => session.set("foo", "bar"))` | The method name is the same everywhere; only the lambda syntax of the host language differs. ## When to reach for it, and when not to 1. **Reach for it** to compute a value from attributes already in the session, to tidy up data a feeder supplied, or to inspect state while developing. 2. **Do not reach for it** to perform I/O of any kind. If the scenario needs a call, the call belongs in the chain as an action, where Gatling can schedule it, time it and report it. 3. **Do not reach for it** to "fix" something the DSL already expresses. A conditional block, a loop or a check expresses intent to the engine; a hand-rolled equivalent inside a function does not. ## What the returned session affects The session your function returns is what every later step in that virtual user's scenario sees, and only that. Attributes are per virtual user: nothing you set becomes visible to another user, and nothing survives past the end of that user's run through the scenario. If you return the wrong object — the session as it was before your `set` call, say — the change is simply lost, silently, in the same way a discarded builder is lost. ## What an interviewer is listening for The cheap answer is "it lets you edit the session". The answer that lands adds the two constraints and the reason behind them: the functions run in Gatling's shared threads because there is no thread per user, and the SDK is a set of definitions rather than commands, so building one inside a function is dead code.
- Why is a println inside a Gatling session function dangerous under load?Standard output is a slow blocking sink, and these functions run on the engine's shared threads rather than on a thread of their own. At a few users you notice nothing; at a few thousand the writes serialise and the generator stalls, so the numbers you collect describe your logging, not the system under test.
- Must a Gatling session function return a different Session instance?No. It must return a `Session`, and returning the one it received is a valid no-change step. What it must not do is mutate in place: `Session` is immutable, so `set` yields a new instance and you have to return that instance for the change to be visible downstream.
saying these in an interview costs you the question
- Calling a remote API inside an exec session function
- Expecting an http builder inside the function to fire
- Forgetting to return the session that set produced
- Assuming each virtual user has its own thread