Authors Gatling simulations in Java / Kotlin / Scala (or JS / TS) using the Simulation class plus http() / scenario() / exec() DSL builders, ramps virtual users via injectOpen (arrival rate) or injectClosed (concurrent count), runs via Maven / Gradle / sbt with the Gatling plugin, and gates CI on assertions defined in setUp(). Use when the project is on the JVM and the team prefers code-first load tests over JMeter's XML or k6's JavaScript-only authoring.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Gatling tests are Simulation classes in Java / Kotlin / Scala /
JS / TS that compose http() / scenario() / exec() DSL builders
and run via the Gatling Maven / Gradle / sbt plugin (per
gatling-tutorial). Supported protocols span HTTP,
WebSocket, Server-Sent Events, JMS, gRPC, and MQTT
(gatling-readme).
For pure HTTP load testing on a non-JVM stack, prefer
k6-load-testing (JavaScript) or
locust-load-testing (Python).
The current version + matching plugin is documented at
docs.gatling.io - pin to a specific
release rather than LATEST. The minimum dependencies for a Maven
project are the Gatling Maven plugin (build) plus
gatling-charts-highcharts (test scope, for HTML report generation).
For Gradle / sbt setups, the equivalent plugins are
gatling-gradle-plugin and sbt-gatling. See
gatling-tutorial for the canonical project-init flow.
Per gatling-tutorial, every Gatling test extends
Simulation and uses three DSL builders:
| Builder | Purpose |
|---|---|
http(...) | HTTP protocol config: base URL, default headers, share-connection settings. |
scenario(...) | A named sequence of user actions. |
exec(...) | Executes one request or a chain of actions within a scenario. |
Java example:
package com.example.load;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
public class OrdersSimulation extends Simulation {
HttpProtocolBuilder httpProtocol = http
.baseUrl("https://staging.example.com")
.acceptHeader("application/json")
.header("Authorization", "Bearer " + System.getenv("API_TOKEN"));
ScenarioBuilder ordersScenario = scenario("Order lifecycle")
.exec(
http("Create order")
.post("/orders")
.body(StringBody("{\"sku\":\"SKU-1\",\"qty\":2}"))
.check(status().is(201))
.check(jsonPath("$.order_id").saveAs("orderId"))
)
.pause(1)
.exec(
http("Read order")
.get("/orders/#{orderId}")
.check(status().is(200))
);
{
setUp(
ordersScenario.injectOpen(
rampUsersPerSec(1).to(20).during(Duration.ofMinutes(1)),
constantUsersPerSec(20).during(Duration.ofMinutes(2))
)
)
.protocols(httpProtocol)
.assertions(
global().responseTime().percentile(95).lt(500),
global().failedRequests().percent().lt(1.0)
);
}
}(Adapted from gatling-tutorial DSL primitives.)
Per gatling-tutorial:
injectOpenNew users arrive continuously during the test window. Use when modeling realistic traffic that doesn't depend on user response time.
ordersScenario.injectOpen(
nothingFor(Duration.ofSeconds(5)), // warmup grace
rampUsersPerSec(1).to(50).during(Duration.ofMinutes(1)), // ramp to 50 RPS over 1 min
constantUsersPerSec(50).during(Duration.ofMinutes(5)) // hold 50 RPS for 5 min
)injectClosedA fixed pool of users repeats actions. Use when modeling sessions / connection-pool behavior where total concurrency matters more than arrival rate.
ordersScenario.injectClosed(
rampConcurrentUsers(0).to(100).during(Duration.ofMinutes(1)),
constantConcurrentUsers(100).during(Duration.ofMinutes(5))
)Most public APIs are Open; session-bound systems (databases, video streams) are Closed.
Per gatling-tutorial, setUp().assertions(...) defines
the CI gate criteria. Every assertion is a chain of selectors:
| Selector | What it asserts |
|---|---|
global().responseTime().percentile(95).lt(500) | Global p95 response time < 500 ms. |
global().failedRequests().percent().lt(1.0) | < 1% of requests failed globally. |
details("Create order").requestsPerSec().gte(20) | Specific request name throughput. |
forAll().responseTime().mean().lt(300) | Mean across every named request < 300ms. |
Failed assertions cause Gatling to exit non-zero - the canonical CI gate.
mvn gatling:test # runs all simulations
mvn gatling:test -Dgatling.simulationClass=com.example.load.OrdersSimulationThe plugin places HTML reports under target/gatling/<simulation>-<timestamp>/.
./gradlew gatlingRun # runs all
./gradlew gatlingRun-com.example.load.OrdersSimulation # runs onesbt 'Gatling/test'Per gatling-readme, each run produces an HTML report under
<output>/<simulation>-<timestamp>/index.html with:
For machine-readable output, parse <output>/.../js/stats.json -
contains the same data the HTML report renders.
Full GitHub Actions workflow (PR + nightly, report uploaded via
if: always()) in
references/ci-integration.md. A failed
assertion exits the Maven build non-zero.
See references/anti-patterns.md: wrong
injection model for the traffic, hardcoded URLs/tokens, missing
pause(), asserting only failedRequests, synthetic spikes, and
per-iteration re-authentication.
k6-load-testing,
jmeter-load-testing,
locust-load-testing -
alternatives by stack.perf-budget-gate - downstream
gate that aggregates load-runner verdicts with frontend perf
metrics.