Gatling Java SDK — From Template to Production-Ready Simulations
Why this guide exists
Build on the fast-start information from the Installation Guide and learn how to assemble production-ready scripts. This page collects the core building blocks—scenarios, feeders, checks, workload models, and workflow hygiene—without walking through every editor action. If you need a slower pace, fall back to Create your first Java-based simulation.
What you will cover
- Anatomy of a maintainable simulation file.
- Feeding data and correlating dynamic responses.
- Choosing injection profiles that mimic real traffic.
- Operating tips: reports, troubleshooting, and governance.
We link to the appropriate reference material whenever we skip implementation details, so you can go deeper on demand.
Prerequisites
- Java LTS runtime (versions 11 through 25 supported, 17 or newer recommended).
- A JVM build tool such as Maven or Gradle (examples below use Maven Wrapper commands).
- A non-production system you are allowed to exercise with load tests.
- Optional: a Gatling Enterprise account for distributed execution.
Setup recap
Clone the gatling-java-demo project or adapt an existing Maven test module. Confirm you can run:
./mvnw gatling:test
Need help with IDE configuration or directory layout? Revisit the Installation Guide before continuing.
Understand the core concepts
| Concept | Description | Dig deeper |
|---|---|---|
| Simulation | Executable performance test class that orchestrates your scenarios and injection profiles. | Simulation concepts |
| Protocol | Shared configuration (e.g., base URL, headers) applied to one or more scenarios. | HTTP protocol reference |
| Scenario | Virtual user behaviour—a sequence of actions that represents a workflow. | Scenario SDK reference |
| Feeder | Test data source (CSV, JSON, JDBC, custom code). | Feeder reference |
| Checks & Assertions | Response validations and pass/fail thresholds for your run. | Checks, Assertions |
| Injection Profile | Defines the arrival rate and ramp-up strategy for virtual users. | Injection SDK reference |
Mental model: translate your business journey into scenarios, feed them data, run users through an injection profile, and guard the outcome with assertions.
Assemble a baseline simulation
Start from a single-scenario template, then break logic into helpers as the script grows.
Baseline example
package example;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
public class GetStartedSimulation extends Simulation {
// Define HTTP configuration
// Reference: https://docs.gatling.io/reference/script/http/protocol/
HttpProtocolBuilder httpProtocol =
http.baseUrl("https://api-ecomm.gatling.io")
.acceptHeader("application/json")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36");
// Define scenario
// Reference: https://docs.gatling.io/concepts/scenario/
ScenarioBuilder scenario =
scenario("Scenario").exec(http("Session").get("/session"));
// Define injection profile and execute the test
// Reference: https://docs.gatling.io/concepts/injection/
{
setUp(scenario.injectOpen(constantUsersPerSec(2).during(60))).protocols(httpProtocol);
}
}Key points:
- Keep protocol builders immutable, then share them across scenarios with
.protocols(). - Use
System.getPropertyfor quick parameterization (-Dusers=100). For richer configuration, graduate to Typesafe Config or dedicated Java classes (see the configuration guide).
Enrich scenarios with data and business behavior
Feeders: avoid hot-cache artifacts
package example;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.core.FeederBuilder;
public class FeederExample {
// Feeder reading src/test/resources/data/products.csv
public static FeederBuilder<String> productFeeder = csv("data/products.csv").circular();
public static ScenarioBuilder browseFeeder =
scenario("Browse With Data")
.feed(productFeeder)
.exec(http("Search ${term}")
.get("/search")
.queryParam("q", "${term}")
.check(status().is(200)));
}- Store CSV/JSON files under
src/test/resources/dataso they ride the classpath. - Pick the right strategy (
.circular(),.queue(),.random()) to balance uniqueness and repeatability. More strategies live in the feeder reference.
Correlation: capture dynamic values
package example;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
public class AddWithCsrf{
public static ScenarioBuilder addWithCsrf =
scenario("Add With CSRF")
.exec(http("Get Form")
.get("/account")
.check(status().is(200))
.check(css("input[name='csrfToken']", "value").saveAs("csrf")))
.pause(1)
.exec(http("Post Form")
.post("/account")
.formParam("csrfToken", "#{csrf}")
.formParam("email", "user@example.com")
.check(status().in(200, 302)));
}- Add a check that extracts the token (
saveAs). - Reuse it in later requests with
#{csrf}(string interpolation) or.formParam("csrf", session("csrf"))if you prefer method references. - Keep a
check(status().is(200))near every extractor to fail fast when the app changes. For advanced extractors, see the check builders.
Compose journeys
Break long journeys into smaller chains and reuse them:
ChainBuilder search = exec(http("Search").get("/search").queryParam("q", "#{term}").check(status().is(200)));
ChainBuilder view = exec(http("View").get("/items/#{sku}").check(status().in(200, 304)));
ScenarioBuilder browse = scenario("Browse").feed(productFeeder).exec(search, view);
Declare shared helpers in src/test/java/.../utils and import them from your simulations to keep logic tidy.
Model realistic workloads
Use injection profiles to express how traffic arrives. Combine warm-up, steady state, and cool-down phases to mimic production.
package perf.simulations;
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 DualJourneySimulation extends Simulation {
private static final String BASE_URL = System.getProperty("baseUrl", "https://ecomm.gatling.io");
HttpProtocolBuilder httpProtocol = http
.baseUrl(BASE_URL)
.acceptHeader("application/json, text/html;q=0.9,*/*;q=0.8")
.userAgentHeader("Gatling/Java DualJourney");
FeederBuilder.Batchable<String> searchFeeder = csv("data/search_terms.csv").random();
ScenarioBuilder browse =
scenario("01 Browse")
.feed(searchFeeder)
.exec(http("Home").get("/").check(status().is(200)))
.pause(1, 3)
.exec(http("Search ${term}").get("/search").queryParam("q", "#{term}")
.check(status().is(200)))
.pause(1, 2)
.exec(http("View ${term}").get("/items/${term}").check(status().in(200, 304)));
ScenarioBuilder purchase =
scenario("02 Purchase")
.exec(http("Get CSRF").get("/checkout")
.check(status().is(200))
.check(regex("name=\"csrf\" value=\"([^\"]+)\"").saveAs("csrf")))
.pause(1)
.exec(http("Submit Order").post("/checkout")
.formParam("csrf", "#{csrf}")
.formParam("sku", "SKU-12345")
.formParam("qty", "1")
.check(status().in(200, 302)))
.pause(2);
{
setUp(
browse.injectOpen(
rampUsers(50).during(60), // ramp to 50
constantUsersPerSec(10).during(180) // steady 3 minutes
).protocols(httpProtocol),
purchase.injectOpen(
rampUsers(10).during(60),
constantUsersPerSec(2).during(180)
).protocols(httpProtocol)
).assertions(
global().successfulRequests().percent().gt(99.0),
forAll().responseTime().percentile3().lt(1500),
details("02 Purchase", "Submit Order").successfulRequests().percent().gt(98.0)
);
}
}Highlights:
- Mix scenarios with distinct arrival rates to mirror different user personas.
- Detect regressions with assertions on
global()anddetails("scenario", "request"). - Keep human-readable names (
"01 Browse") so reports stay sorted and readable.
Common injection shortcuts:
atOnceUsers(x)for smoke tests or spikes.rampUsers(x).during(t)to smooth into load.constantUsersPerSec(rate).during(t)when you care about arrival rate more than concurrent sessions.heavisideUsers(x).during(t)for S-curve ramps that avoid sudden jumps.
Prefer pacing by arrival rate or closed workload models? Study the injection SDK reference and closed models guide.
Run and inspect results
- Run locally with
./mvnw -Dgatling.simulationClass=… gatling:testor interactive mode (./mvnw gatling:test). - After each run, open
target/gatling/<simulation>-<timestamp>/index.htmland focus on p95/p99 latency, throughput, per-request errors, and response time distribution. - Need to automate? Wire the same command into CI, or explore Gatling Enterprise for distributed runs and real-time dashboards.
Troubleshooting checklist
- Connection failures: verify base URL, DNS, VPN/proxy rules, and SSL trust stores.
- Server 429/503 responses: coordinate with ops and honour rate limits—reduce load or widen ramp.
- Too-perfect results: add
pause()and realistic think times; confirm you are exercising business-critical endpoints, not just static assets. - Data collisions: switch feeders to
.queue()or generate unique IDs per user; reset test data between runs.
Operational hygiene
- Version your tests alongside application code and review them like any other pull request.
- Externalize secrets via environment variables or the Gatling Enterprise console—never hard-code credentials.
- Document target SLOs in code using assertions so CI builds surface performance regressions immediately.
- Share knowledge: keep README notes or ADRs that explain scenarios, target metrics, and known limitations.
Where to go next
- Want a slower, instructional pace? Follow Create your first Java-based simulation.
- Need IDE, packaging, or Maven plugin help? Revisit the Installation Guide or the gatling-maven-plugin guide.
- Move beyond HTTP with the protocol guides and expand checks using the Java SDK reference.
- Ready for team-wide load infrastructure? Scale out, share dashboards, and automate governance with Gatling Enterprise.