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.