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.getProperty for 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/data so 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() and details("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:test or interactive mode (./mvnw gatling:test).
  • After each run, open target/gatling/<simulation>-<timestamp>/index.html and 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

Edit this page on GitHub