02 — Core Concepts
Simulation anatomy
A Gatling simulation is a class that extends Simulation. It has three parts:
Simulation
├── Protocol definition — how to talk to the system (HTTP base URL, headers, …)
├── Scenario(s) — what virtual users do (steps)
└── setUp block — wire scenarios + protocols + injection profiles
Java skeleton
public class MySimulation extends Simulation {
// 1. Protocol
HttpProtocolBuilder httpProtocol = http.baseUrl("https://api.example.com");
// 2. Scenario
ScenarioBuilder scn = scenario("My scenario")
.exec(http("Home").get("/"));
// 3. setUp — runs once when the simulation starts
{
setUp(
scn.injectOpen(rampUsers(100).during(Duration.ofSeconds(30)))
).protocols(httpProtocol);
}
}
Kotlin skeleton
class MySimulation : Simulation() {
val httpProtocol = http.baseUrl("https://api.example.com")
val scn = scenario("My scenario")
.exec(http("Home").get("/"))
init {
setUp(
scn.injectOpen(rampUsers(100).during(30))
).protocols(httpProtocol)
}
}
Virtual users
Each virtual user is a lightweight coroutine (not a thread). Gatling can run tens of thousands of concurrent virtual users on a single machine because they share a small thread pool.
Virtual user lifecycle:
start → execute scenario steps → end
Each step is async; the engine schedules them without blocking OS threads.
Scenario
A ScenarioBuilder is a blueprint — it describes what each user will do. The actual users are created later by the injection profile.
ScenarioBuilder scn = scenario("Browse shop")
.exec(http("Home page").get("/"))
.pause(2) // think time
.exec(http("Products").get("/products"))
.pause(1, 3) // random pause between 1-3 s
.exec(http("Product detail").get("/products/42"));
Session
Each virtual user has its own Session — a key/value map that persists for the lifetime of that user's scenario run. It is used to:
- Store extracted response values (e.g. auth tokens)
- Drive conditional logic
- Pass data between steps
See 08 — Session for details.
Checks
After every request Gatling can verify the response. A check failure marks the request as KO (failed) in the report but does not stop the simulation by default.
http("Login").post("/login")
.check(status().is(200))
.check(jsonPath("$.token").saveAs("authToken"))
See 06 — Checks for details.
Protocol
The protocol builder configures shared behaviour for all requests:
HttpProtocolBuilder httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json")
.contentTypeHeader("application/json")
.userAgentHeader("Gatling/3.11");
One simulation can use multiple protocols assigned to different scenarios.
setUp block
setUp is where everything is assembled. It returns an SetUp object that accepts global assertions:
{
setUp(
scn.injectOpen(rampUsers(200).during(Duration.ofSeconds(60)))
)
.protocols(httpProtocol)
.assertions(
global().responseTime().percentile(95).lt(500), // p95 < 500 ms
global().successfulRequests().percent().gt(99.0) // >99% success rate
);
}
If any assertion fails, the simulation exits with a non-zero code — handy for CI.
Request naming and grouping
Request names appear in the report. Use descriptive names and group related requests:
scenario("Checkout flow")
.group("Browse").on(
exec(http("Home").get("/"))
.exec(http("Search").get("/search?q=boots"))
)
.group("Checkout").on(
exec(http("Add to cart").post("/cart"))
.exec(http("Pay").post("/checkout/pay"))
);
Groups aggregate metrics separately in the report.