skip to content

In a Gatling simulation, which run settings attach to setUp itself rather than to one population, and what wins when both are set?

level: seniorimportance: should knowfreq 38%

answer

  1. two places to hang configuration
  2. assertions and maxDuration are setUp only
  3. andThen exists only on a population
  4. population protocol overlays the global one
  5. protocols are indexed by protocol class

basics

~20 s

Assertions and maxDuration exist only on the setUp handle; protocols, the pause policy and throttling exist on both setUp and each population; andThen exists only on a population. Where a protocol is set in both places, the population's setting wins.

solid answer

~40 s

`setUp` returns a handle for run-wide settings, and populations carry a partly overlapping set of their own. `assertions(...)` and `maxDuration(...)` are handle-only — there is no per-population equivalent. `protocols(...)`, the pause-policy methods and `throttle(...)` exist in both places. `andThen(...)` is population-only. Protocols are indexed **by protocol class**, and Gatling resolves a population's set as the global map overlaid by that population's own, so a population-level HTTP configuration replaces the setUp-level one for that population and leaves it in place for the others. Omitting protocols altogether is not an error: Gatling falls back to a default protocol value built from configuration, which is how a simulation with no base URL still starts and only fails once a request goes out.

code

java · 26 lines
java
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import java.time.Duration;

public class TwoServicesSimulation extends Simulation {

  HttpProtocolBuilder shared = http.acceptHeader("application/json");

  HttpProtocolBuilder catalogue = http.baseUrl("https://catalogue.example.com");

  ScenarioBuilder browse = scenario("browse").exec(http("list").get("/items"));

  ScenarioBuilder health = scenario("health").exec(http("ping").get("/health"));

  {
    setUp(
      // replaces the shared HTTP configuration for this population, headers included
      browse.injectOpen(atOnceUsers(10)).protocols(catalogue),
      health.injectOpen(atOnceUsers(1))
    ).protocols(shared)
     .maxDuration(Duration.ofMinutes(10));
  }
}

go deeper

for a junior

Be able to attach an HTTP protocol at the setUp level and know that a population can carry its own instead.

for a middle

Be ready to say which settings have no per-population form, and to explain that a population's protocol overlays rather than merges with the global one.

for a senior

Be ready to review a multi-scenario simulation for silent misconfiguration: a lost second protocols call, or a default protocol standing in for one nobody set.

for a principal

Own the convention for where configuration lives across a team's simulations, so shared settings and deliberate per-population overrides stay distinguishable.

A Gatling simulation has two places to hang configuration: the handle `setUp` returns, and each `PopulationBuilder` inside it. Knowing which settings live where — and which of them merge, replace or override — is what separates a simulation that scales to several scenarios from one that quietly runs half of them against the wrong host. ## Where each setting lives | setting | on the `setUp` handle | on a population | |---|---|---| | `protocols(...)` | yes, applied to every population | yes, applied to that one | | pause policy (`disablePauses`, `constantPauses`, `uniformPauses`, …) | yes | yes | | `throttle(...)` | yes | yes | | `assertions(...)` | yes | **no** | | `maxDuration(...)` | yes | **no** | | `andThen(...)` | **no** | yes | The asymmetry is deliberate. Assertions judge the run as a whole and a verdict is a single thing, so there is nowhere else to put them. `maxDuration` bounds wall-clock time for the entire simulation, so it cannot be per-population either. `andThen`, conversely, describes a relationship *between* populations, so it can only be expressed on one of them. ## How protocols resolve Protocols are stored **indexed by their concrete class**. Two consequences follow: 1. A population can hold at most one HTTP protocol configuration, one JMS configuration, and so on. Registering a second `HttpProtocolBuilder` does not merge the two — it takes the slot. 2. When a population is built, Gatling starts from the setUp-level map and overlays the population's own map on top. For any protocol class present in both, **the population's entry wins**; classes present only at the setUp level are still inherited. So the useful pattern for a simulation that drives two different services is a shared protocol on `setUp` for everything common, and a per-population protocol only where a population genuinely differs. And if you configure no protocol at all, nothing fails at registration: Gatling computes a default protocol value from configuration and hands it over. A missing `baseUrl` therefore shows up as failed requests during the run, not as a startup error. ## The one behavioural difference between the SDKs `SetUp` is mutable, and the Java API and the Scala core mutate it differently when a chained method is called twice: - **Scala** merges. `protocols` folds the new entries into what is already registered, and `assertions` appends to the existing list. - **The Java API replaces.** A second `protocols(...)` call rebuilds the map from only that call's arguments, and a second `assertions(...)` call overwrites the previous list. For Java, Kotlin, JavaScript and TypeScript this means one call each, listing everything. Splitting `setUp(...).protocols(httpProtocol).protocols(jmsProtocol)` across two calls silently loses the first — an easy mistake when the two protocol builders are produced by different helper methods. Write `.protocols(httpProtocol, jmsProtocol)` instead. ## What maxDuration actually does `maxDuration` schedules a timer when the run starts and stops the load generator when it fires. Points worth being precise about: - It clocks **the whole run**, from the moment injection begins, so it covers every `andThen` generation. A parent that overruns can eat the budget and leave the children unstarted. - It stops the run **gracefully**. The reason Gatling records is a max-duration-reached stop, classified alongside a normal completion rather than as a crash, so the run still produces its output. - It does not wait for anything. Virtual users still in flight are stopped; that is the entire point, and the documented use is to bound a run whose duration you cannot predict. - In the Java API the `long` overload is **seconds** — `maxDuration(30)` is thirty seconds, not thirty minutes and not thirty milliseconds. Prefer the `Duration` overload to make the unit explicit. In JavaScript and TypeScript the argument is a duration object such as `{ amount: 10, unit: "minutes" }`. - The effective ceiling is the **smallest** bound in play: Gatling takes the minimum of the `maxDuration` you set and the duration of any throttling profile registered on the run or on a scenario. Setting a generous `maxDuration` does not extend a shorter bound that came from elsewhere. ## The review checklist - Is every population's target host determined by a protocol you can point at, rather than by a default? - Is there exactly one `protocols(...)` call per level in Java, Kotlin or JavaScript? - Does `maxDuration` leave room for the last `andThen` generation to run, not just the first? - Are per-population overrides genuine differences, or copy-paste that will drift from the shared setting?

  • What happens to a Gatling run when maxDuration fires while users are still working?
    Gatling stops the load generator and records a max-duration-reached stop, which it classifies as a graceful reason rather than a crash. The run therefore ends normally and still produces its output; the users in flight are simply not allowed to finish. That is the documented purpose of the setting.
  • Why can a Gatling simulation with no protocols call still start successfully?
    Because protocol lookup falls back to a default value computed from configuration rather than failing. Registration and startup succeed, the population builds, and the absence only becomes visible when a request needs a base URL it has not been given. Nothing in the registration phase will warn you.
  • Which settings can be attached to both setUp and an individual Gatling population?
    Protocol configuration, the pause policy and throttling. Assertions and `maxDuration` are available only on the `setUp` handle, and `andThen` only on a population. Where protocols are set in both places, the population's entry replaces the global one for that protocol class and for that population alone.

saying these in an interview costs you the question

  • Expecting a per-population maxDuration to exist
  • Assuming a population protocol merges with the global one
  • Chaining two protocols calls in Java and expecting both to survive
  • Reading Java's maxDuration(30) as thirty minutes