Command Palette

Search for a command to run...

Unit 5.2 · Performance Engineering

JMeter and Gatling (and When to Use Which Tool)

JMeter's thread groups, samplers, and non-GUI runs; Gatling's code-first simulations and injection profiles; distributed load; and choosing between k6, JMeter, Gatling, and Locust.

Intermediate 45 min 3 lab steps 1 failure drills

Start here

The mental model

All load tools do the same three things: describe a user journey, decide how many users arrive and when, and record what happened. They differ in how you write the journey (GUI vs code), which protocols they speak, and how efficiently they generate load. You'll meet JMeter in older enterprise and Java shops, Gatling in JVM teams that want code, and k6 in cloud-native teams. Knowing all three makes you useful anywhere.

Go deeper

How it works inside

01JMeter

A Java desktop tool (since 1998) where a TEST PLAN is a tree: THREAD GROUPS (virtual users, ramp-up, loops or duration) contain SAMPLERS (HTTP request, JDBC, JMS, gRPC via plugins), CONFIG ELEMENTS (CSV data sets, cookie and header managers), TIMERS (think time), ASSERTIONS, and LISTENERS (results). Plans are saved as .jmx XML.

Build and debug in the GUI with a few users; RUN in non-GUI mode (jmeter -n), because the GUI and live listeners consume huge amounts of memory. The HTML dashboard report (-e -o) gives percentiles and throughput over time. Thread groups are closed-model by default; the Concurrency/Arrivals Thread Groups (plugins) and the Throughput Shaping Timer give open-model control. Distributed mode uses one controller and several workers over RMI.

02Gatling

Gatling simulations are code (Java, Kotlin, Scala, or JavaScript/TypeScript), so they live in Git, get reviewed, and reuse functions. Its engine is asynchronous (Netty/Akka-style), so one machine simulates many users cheaply. INJECTION PROFILES are explicit about open vs closed models: rampUsersPerSec(10).to(200).during(3 min) is open; constantConcurrentUsers(100) is closed. ASSERTIONS fail the build like k6 thresholds, and the HTML report is excellent.

03Distributed load and the cloud

When one generator isn't enough (Unit 5.1 drill), spread load across several: JMeter distributed mode, the k6 Operator (runs a test as parallel pods in Kubernetes), Gatling Enterprise, Locust's master/workers, or managed services (Grafana Cloud k6, Azure Load Testing, AWS Distributed Load Testing solution). Aggregate percentiles correctly: you can't average p99s from several generators; you need the merged distributions, which these tools handle.

Do it

Hands-on lab

  1. 1

    Run a JMeter plan headless

    A minimal plan (build it in the GUI once, then commit the .jmx). -J passes properties so the same plan runs against any environment and load level.

    checkout.jmx (excerpt)whole filexml
    <ThreadGroup testname="Shoppers">
      <stringProp name="ThreadGroup.num_threads">${__P(users,50)}</stringProp>
      <stringProp name="ThreadGroup.ramp_time">${__P(rampup,60)}</stringProp>
      <boolProp name="ThreadGroup.scheduler">true</boolProp>
      <stringProp name="ThreadGroup.duration">${__P(duration,300)}</stringProp>
    </ThreadGroup>
    <hashTree>
      <CSVDataSet testname="Users CSV"><stringProp name="filename">users.csv</stringProp></CSVDataSet>
      <HTTPSamplerProxy testname="GET /products">
        <stringProp name="HTTPSampler.domain">${__P(host,localhost)}</stringProp>
        <stringProp name="HTTPSampler.path">/api/products?q=${query}</stringProp>
      </HTTPSamplerProxy>
      <UniformRandomTimer testname="Think time"><stringProp name="RandomTimer.range">2000</stringProp></UniformRandomTimer>
    </hashTree>
    terminal
    $ jmeter -n -t checkout.jmx -Jhost=staging.shoplite.dev -Jusers=200 -Jduration=600 -l results.jtl -e -o report/
    grep -A1 'summary =' jmeter.log | tail -2
    ── expected output ──
    summary = 184233 in 00:10:00 = 307.1/s Avg: 188 Min: 12 Max: 4102 Err: 611 (0.33%)
    Tidying up ... @ Sat Sep 27 11:42:10 IST 2026
  2. 2

    The same journey in Gatling (Java DSL)

    An open-model ramp with assertions. Run with the Maven/Gradle plugin; the build fails if assertions fail.

    src/test/java/shoplite/CheckoutSimulation.javawhole filejava
    package shoplite;
    
    import static io.gatling.javaapi.core.CoreDsl.*;
    import static io.gatling.javaapi.http.HttpDsl.*;
    import io.gatling.javaapi.core.*;
    import io.gatling.javaapi.http.*;
    import java.time.Duration;
    
    public class CheckoutSimulation extends Simulation {
      HttpProtocolBuilder http = http.baseUrl(System.getProperty("baseUrl", "http://localhost:8080"))
          .acceptHeader("application/json");
    
      FeederBuilder<String> users = csv("users.csv").random();
    
      ScenarioBuilder shop = scenario("Browse and checkout")
          .feed(users)
          .exec(http("search").get("/api/products?q=#{query}").check(status().is(200)))
          .pause(Duration.ofSeconds(1), Duration.ofSeconds(3))
          .exec(http("add to cart").post("/api/cart").body(StringBody("{\"sku\":\"MUG-RED\"}")).asJson())
          .exec(http("checkout").post("/api/checkout").check(status().in(200, 201)));
    
      {
        setUp(shop.injectOpen(
                rampUsersPerSec(5).to(150).during(Duration.ofMinutes(3)),
                constantUsersPerSec(150).during(Duration.ofMinutes(5))))
            .protocols(http)
            .assertions(
                global().responseTime().percentile(95.0).lt(400),
                global().failedRequests().percent().lt(1.0));
      }
    }
    terminal
    $ ./mvnw gatling:test -Dgatling.simulationClass=shoplite.CheckoutSimulation -DbaseUrl=https://staging.shoplite.dev
    ── expected output ──
    ---- Global Information --------------------------------------------------------
    > request count 91520 (OK=91204 KO=316 )
    > response time 95th percentile (ms) 361 (OK=355 KO=5001 )
    > mean requests/sec 190.6 (OK=190 KO=0.66 )
    Global: 95th percentile of response time is less than 400 : true
    Global: percentage of failed events is less than 1.0 : true
  3. 3

    Distributed k6 on Kubernetes

    The k6 Operator runs a script as N parallel pods and splits the load between them. parallelism: 4 quadruples generator capacity.

    k6-testrun.yamlwhole fileyaml
    apiVersion: k6.io/v1alpha1
    kind: TestRun
    metadata:
      name: black-friday-rehearsal
    spec:
      parallelism: 4
      script:
        configMap: { name: loadtest, file: loadtest.js }
      arguments: -e BASE_URL=https://staging.shoplite.dev

Operate it

Knobs that matter

SettingDefaultWhat it doesWhen to change it
JMeter heap (HEAP env)-Xms1g -Xmx1gMemory for the JMeter JVM.Raise for big tests; keep listeners out of non-GUI runs.
JMeter -l results.jtl formatCSVRaw results file.CSV with only needed fields; avoid saving response data.
Gatling injectOpen vs injectClosed—Open (arrival rate) vs closed (concurrency) model.Open for internet-facing services; closed for systems with a fixed user pool (call-centre apps).

3am practice

Failure drills

Each drill is a real failure mode. Read the scenario and the output, decide what's wrong, then reveal the diagnosis.

Drill #1

JMeter runs out of memory mid-test

A 30-minute test run from the JMeter GUI with a 'View Results Tree' listener stops responding after 8 minutes.

terminal
$ tail -2 jmeter.log
── what you'll see ──
ERROR o.a.j.JMeter: Uncaught exception in thread Thread[#412,StandardJMeterEngine,5,main]
java.lang.OutOfMemoryError: Java heap space

Decide

Choosing a load tool

ToolTests written inStrengthsWatch out for
k6JavaScript (Go engine)Developer-friendly, thresholds, CI, Grafana, k6 OperatorNot a browser or full Node.js runtime; protocol extensions via xk6
JMeterGUI → XML (.jmx)Many protocols (JDBC, JMS, FTP), huge plugin ecosystem, familiar in enterprisesXML in Git is hard to review; heavy per thread
GatlingJava/Kotlin/Scala/JSEfficient async engine, great reports, code reuseJVM toolchain; enterprise features are paid
LocustPythonEasy scripting for Python teams, distributed modeLess efficient per core; closed model by default

The bigger picture

Connects to

Prove it

Interview questions

01

Why run JMeter in non-GUI mode?

02

How do you generate more load than one machine can produce?

0/3 · 0%