What Is Gatling?

Gatling is a high-performance, open-source load testing tool designed for continuous integration and developer-friendly workflows. Built on Scala with an asynchronous, non-blocking architecture (Akka + Netty), Gatling can simulate thousands of concurrent users with significantly lower memory consumption than thread-based tools like JMeter.

Gatling produces detailed, interactive HTML reports out of the box — often considered the best-looking performance test reports in the industry. These reports include response time distributions, percentiles, request counts over time, and error analysis.

When to Choose Gatling

Each load testing tool has its strengths. Here is how Gatling compares.

FeatureGatlingJMeterk6
LanguageScala/Java/Kotlin DSLGUI + XMLJavaScript
ArchitectureAsync non-blockingThread per userGo goroutines
ReportsBeautiful HTML built-inBasic, needs pluginsTerminal + JSON/CSV
CI/CDMaven/Gradle/sbt pluginCLI modeCLI native
ProtocolHTTP, WebSocket, JMS, MQTTHTTP, JDBC, FTP, LDAP, JMSHTTP, WebSocket, gRPC
Resource usageVery lowHighLow
Learning curveSteep (Scala)ModerateEasy (JS)

Choose Gatling when: You need excellent reporting, your team uses JVM languages, you want low resource usage at scale, or you need native Maven/Gradle integration for CI/CD.

Gatling Simulation Structure

A Gatling simulation is a Scala class that extends Simulation. It contains three main parts:

  1. Protocol configuration — HTTP settings, base URL, headers
  2. Scenario definition — The user journey (sequence of requests)
  3. Injection profile — How users are added over time
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class BasicSimulation extends Simulation {

  // 1. Protocol configuration
  val httpProtocol = http
    .baseUrl("https://api.example.com")
    .acceptHeader("application/json")
    .contentTypeHeader("application/json")

  // 2. Scenario definition
  val scn = scenario("Browse Products")
    .exec(
      http("List Products")
        .get("/api/products")
        .check(status.is(200))
        .check(jsonPath("$.products").exists)
    )
    .pause(2)
    .exec(
      http("Product Details")
        .get("/api/products/1")
        .check(status.is(200))
        .check(jsonPath("$.name").saveAs("productName"))
    )
    .pause(1)

  // 3. Setup with injection profile
  setUp(
    scn.inject(
      rampUsers(100).during(60.seconds)
    )
  ).protocols(httpProtocol)
   .assertions(
     global.responseTime.percentile3.lt(1000),
     global.successfulRequests.percent.gt(99)
   )
}

Protocol Configuration

The HTTP protocol builder defines shared settings for all requests:

val httpProtocol = http
  .baseUrl("https://api.example.com")    // base URL for all requests
  .acceptHeader("application/json")       // default Accept header
  .acceptEncodingHeader("gzip, deflate")  // compression
  .userAgentHeader("Gatling/3.9")         // user agent
  .shareConnections                        // connection pooling

Scenario Definition

A scenario is a sequence of actions that a virtual user performs:

val scn = scenario("User Journey")
  .exec(http("Homepage").get("/"))
  .pause(1, 3)  // random pause between 1-3 seconds
  .exec(http("Login").post("/login")
    .body(StringBody("""{"user":"test","pass":"123"}"""))
    .check(jsonPath("$.token").saveAs("authToken"))
  )
  .exec(http("Dashboard").get("/dashboard")
    .header("Authorization", "Bearer ${authToken}")
  )

Key actions:

  • exec() — Execute an HTTP request
  • pause() — Wait between requests (think time)
  • repeat() — Loop a sequence N times
  • during() — Loop for a specified duration
  • doIf() / doIfOrElse() — Conditional execution
  • feed() — Inject data from a feeder (CSV, JSON, JDBC)

Feeders (Data Parameterization)

Feeders supply test data to virtual users:

// CSV feeder
val csvFeeder = csv("users.csv").circular  // recycle when exhausted

// JSON feeder
val jsonFeeder = jsonFile("products.json").random

// Inline feeder
val inlineFeeder = Iterator.continually(Map(
  "username" -> s"user_${scala.util.Random.nextInt(1000)}",
  "email" -> s"user${scala.util.Random.nextInt(1000)}@test.com"
))

val scn = scenario("Parameterized Test")
  .feed(csvFeeder)
  .exec(http("Login")
    .post("/login")
    .body(StringBody("""{"username":"${username}","password":"${password}"}"""))
  )

Checks and Assertions

Checks validate individual responses:

http("Get User")
  .get("/api/users/1")
  .check(status.is(200))                         // status code
  .check(jsonPath("$.name").is("John"))           // JSON value
  .check(responseTimeInMillis.lt(500))            // response time
  .check(jsonPath("$.id").saveAs("userId"))       // save for later use
  .check(header("Content-Type").is("application/json"))

Assertions define pass/fail criteria for the entire simulation:

setUp(scn.inject(...))
  .assertions(
    global.responseTime.max.lt(5000),           // max response time < 5s
    global.responseTime.percentile3.lt(1000),   // p75 < 1s
    global.successfulRequests.percent.gt(99),   // > 99% success rate
    details("Login").responseTime.mean.lt(500)  // Login avg < 500ms
  )

Injection Profiles

Injection profiles define how virtual users are introduced into the simulation. Gatling offers several strategies:

setUp(
  scn.inject(
    // Open model (users arrive at a rate)
    nothingFor(5.seconds),                    // wait 5 seconds
    atOnceUsers(10),                          // inject 10 users immediately
    rampUsers(100).during(60.seconds),        // ramp to 100 users over 60s
    constantUsersPerSec(20).during(120.seconds), // 20 users/sec for 2 min
    rampUsersPerSec(1).to(50).during(60.seconds) // ramp from 1 to 50 users/sec
  )
)

These profiles can be combined to create complex load patterns.

Running Gatling

# Using the bundle
./bin/gatling.sh

# Using Maven
mvn gatling:test

# Using Gradle
gradle gatlingRun

# Using sbt
sbt Gatling/test

Gatling generates an HTML report in target/gatling/ with charts for response times, throughput, and active users over time.

Exercise: Design a Gatling Simulation

Create a Gatling simulation for an online auction platform.

Scenario

The auction platform has these endpoints:

EndpointMethodDescription
/api/auth/loginPOSTUser login
/api/auctionsGETList active auctions
/api/auctions/{id}GETAuction details
/api/auctions/{id}/bidPOSTPlace a bid

Requirements

  1. Use a CSV feeder for user credentials
  2. Chain requests: login, browse auctions, view details, place bid
  3. Extract the auth token from login and auction ID from the list
  4. Add pauses between actions (1-3 seconds)
  5. Injection profile: ramp 50 users over 2 minutes, hold for 3 minutes, ramp down
  6. Assertions: p95 < 1000ms, success rate > 98%
Hint: Simulation Structure
class AuctionSimulation extends Simulation {
  val httpProtocol = http.baseUrl("https://auction-api.example.com")
  val csvFeeder = csv("users.csv").circular

  val scn = scenario("Auction Bidding")
    .feed(csvFeeder)
    .exec(/* login */)
    .pause(1, 3)
    .exec(/* list auctions, save auctionId */)
    .pause(1, 2)
    .exec(/* view auction details */)
    .pause(1, 2)
    .exec(/* place bid */)

  setUp(
    scn.inject(
      rampUsers(50).during(120.seconds),
      nothingFor(180.seconds),
      // consider using constantUsersPerSec for steady state
    )
  ).protocols(httpProtocol)
   .assertions(/* your assertions */)
}
Solution: Complete Gatling Simulation
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class AuctionSimulation extends Simulation {

  val httpProtocol = http
    .baseUrl("https://auction-api.example.com")
    .acceptHeader("application/json")
    .contentTypeHeader("application/json")

  val csvFeeder = csv("users.csv").circular

  val scn = scenario("Auction Bidding Flow")
    .feed(csvFeeder)
    // Step 1: Login
    .exec(
      http("Login")
        .post("/api/auth/login")
        .body(StringBody("""{"username":"${username}","password":"${password}"}"""))
        .check(status.is(200))
        .check(jsonPath("$.token").saveAs("authToken"))
    )
    .pause(1, 3)
    // Step 2: List auctions
    .exec(
      http("List Auctions")
        .get("/api/auctions")
        .header("Authorization", "Bearer ${authToken}")
        .check(status.is(200))
        .check(jsonPath("$.auctions[0].id").saveAs("auctionId"))
        .check(jsonPath("$.auctions[0].currentBid").saveAs("currentBid"))
    )
    .pause(1, 2)
    // Step 3: View auction details
    .exec(
      http("Auction Details")
        .get("/api/auctions/${auctionId}")
        .header("Authorization", "Bearer ${authToken}")
        .check(status.is(200))
        .check(jsonPath("$.title").exists)
    )
    .pause(1, 2)
    // Step 4: Place bid
    .exec(session => {
      val currentBid = session("currentBid").as[String].toDouble
      val newBid = currentBid + 10.0
      session.set("newBid", newBid.toString)
    })
    .exec(
      http("Place Bid")
        .post("/api/auctions/${auctionId}/bid")
        .header("Authorization", "Bearer ${authToken}")
        .body(StringBody("""{"amount":${newBid}}"""))
        .check(status.in(200, 201))
    )

  setUp(
    scn.inject(
      rampUsers(50).during(120.seconds),
      nothingFor(180.seconds)
    )
  ).protocols(httpProtocol)
   .assertions(
     global.responseTime.percentile(95).lt(1000),
     global.successfulRequests.percent.gt(98)
   )
}

users.csv:

username,password
bidder1,pass123
bidder2,pass456
bidder3,pass789

Analyzing the HTML Report:

  • Response Time Distribution: Check if the histogram skews left (fast responses)
  • Active Users Over Time: Verify the ramp-up pattern matches your injection profile
  • Requests per Second: Look for throughput stability during steady state
  • Response Time Percentiles: p95 should remain under 1000ms throughout the test
  • Errors tab: Identify which requests fail and at what user count

Pro Tips

  • Java/Kotlin DSL: If your team does not know Scala, Gatling 3.7+ supports Java and Kotlin DSLs that are equally powerful. The Java DSL is the recommended starting point for most teams.
  • Closed vs Open Model: Gatling supports both closed-model (fixed number of concurrent users) and open-model (users arrive at a rate). Use open model for web applications where new users constantly arrive regardless of how many are already active.
  • Gatling Recorder: Similar to JMeter’s recorder, Gatling includes a HAR converter and HTTP proxy recorder for capturing browser traffic and converting it to Gatling scripts.
  • Maven Archetype: Start new projects with mvn archetype:generate -DarchetypeGroupId=io.gatling.highcharts -DarchetypeArtifactId=gatling-highcharts-maven-archetype for a pre-configured project structure.
  • CI Integration: Gatling’s Maven/Gradle plugins make it straightforward to run load tests in Jenkins, GitHub Actions, or GitLab CI. Set assertions to fail the build if performance degrades.