google-site-verification: google61fe8ba583a51912.html
Performance Testing with Gatling in Java

Overview

Gatling is a powerful open-source load testing tool for web applications. While written in Scala, it provides excellent Java DSL support for creating performance tests.

Dependencies

Maven

<properties> <gatling.version>3.9.5</gatling.version> <gatling-maven-plugin.version>4.5.2</gatling-maven-plugin.version> </properties> <dependencies> <dependency> <groupId>io.gatling</groupId> <artifactId>gatling-core</artifactId> <version>${gatling.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.gatling</groupId> <artifactId>gatling-http</artifactId> <version>${gatling.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.gatling.highcharts</groupId> <artifactId>gatling-charts-highcharts</artifactId> <version>${gatling.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>io.gatling</groupId> <artifactId>gatling-maven-plugin</artifactId> <version>${gatling-maven-plugin.version}</version> <configuration> <simulationsFolder>src/test/java</simulationsFolder> </configuration> </plugin> </plugins> </build>

Gradle

plugins { id 'io.gatling.gradle' version '3.9.5' } dependencies { gatling 'io.gatling:gatling-core:3.9.5' gatling 'io.gatling:gatling-http:3.9.5' gatling 'io.gatling.highcharts:gatling-charts-highcharts:3.9.5' }

Basic Gatling Simulation

1. Simple HTTP Load Test

import io.gatling.javaapi.core.*; import io.gatling.javaapi.http.*; import java.time.Duration; import static io.gatling.javaapi.core.CoreDsl.*; import static io.gatling.javaapi.http.HttpDsl.*; public class BasicSimulation extends Simulation { // HTTP protocol configuration HttpProtocolBuilder httpProtocol = http .baseUrl("https://jsonplaceholder.typicode.com") .acceptHeader("application/json") .userAgentHeader("Gatling Performance Test"); // Scenario definition ScenarioBuilder scn = scenario("Basic API Test") .exec( http("Get all posts") .get("/posts") .check(status().is(200)) .check(jsonPath("$[0].id").saveAs("firstPostId")) ) .pause(1) // Pause for 1 second .exec( http("Get specific post") .get("/posts/#{firstPostId}") .check(status().is(200)) .check(jsonPath("$.title").saveAs("postTitle")) ) .exec(session -> { System.out.println("Post Title: " + session.getString("postTitle")); return session; }); // Simulation setup { setUp( scn.injectOpen( nothingFor(5), // Wait 5 seconds atOnceUsers(10), // Inject 10 users at once rampUsers(50).during(30) // Ramp up to 50 users over 30 seconds ) ).protocols(httpProtocol); } }

2. Advanced Simulation with Multiple Scenarios

import io.gatling.javaapi.core.*; import io.gatling.javaapi.http.*; import java.util.concurrent.ThreadLocalRandom; import java.time.Duration; import static io.gatling.javaapi.core.CoreDsl.*; import static io.gatling.javaapi.http.HttpDsl.*; public class AdvancedApiSimulation extends Simulation { // Headers private static final Map<CharSequence, String> HEADERS_JSON = Map.of( "Content-Type", "application/json", "Accept", "application/json" ); // HTTP Protocol HttpProtocolBuilder httpProtocol = http .baseUrl("https://api.example.com") .headers(HEADERS_JSON) .acceptHeader("application/json") .userAgentHeader("Gatling/3.9.5") .disableCaching() .disableFollowRedirect(); // Feeder for test data FeederBuilder<String> userFeeder = csv("data/users.csv").random(); FeederBuilder<String> productFeeder = csv("data/products.csv").circular(); // Common HTTP calls ChainBuilder authenticate = exec( http("Authenticate User") .post("/auth/login") .body(ElFileBody("bodies/login.json")) .check(status().is(200)) .check(jsonPath("$.token").saveAs("authToken")) ); ChainBuilder getAllProducts = exec( http("Get All Products") .get("/products") .header("Authorization", "Bearer #{authToken}") .check(status().is(200)) .check(jsonPath("$[*].id").findRandom().saveAs("randomProductId")) ); ChainBuilder getProductDetail = exec( http("Get Product Detail") .get("/products/#{randomProductId}") .header("Authorization", "Bearer #{authToken}") .check(status().is(200)) .check(jsonPath("$.price").saveAs("productPrice")) ); ChainBuilder createOrder = exec( http("Create Order") .post("/orders") .header("Authorization", "Bearer #{authToken}") .body(ElFileBody("bodies/create_order.json")) .check(status().is(201)) .check(jsonPath("$.orderId").saveAs("orderId")) ); ChainBuilder checkoutOrder = exec( http("Checkout Order") .post("/orders/#{orderId}/checkout") .header("Authorization", "Bearer #{authToken}") .check(status().is(200)) ); // Scenarios ScenarioBuilder browsingUsers = scenario("Browsing Users") .feed(userFeeder) .exec(authenticate) .pause(2) .exec(getAllProducts) .pause(1, 5) // Random pause between 1-5 seconds .exec(getProductDetail) .pause(2) .repeat(3, "viewCount") { // View 3 random products exec(getAllProducts) .exec(getProductDetail) .pause(1) }; ScenarioBuilder purchasingUsers = scenario("Purchasing Users") .feed(userFeeder) .feed(productFeeder) .exec(authenticate) .pause(1) .exec(createOrder) .pause(2) .exec(checkoutOrder); // Setup simulation { setUp( browsingUsers.injectOpen( rampUsersPerSec(1).to(10).during(60), // Ramp up from 1 to 10 users/sec over 1 minute constantUsersPerSec(10).during(120) // Maintain 10 users/sec for 2 minutes ), purchasingUsers.injectOpen( constantUsersPerSec(2).during(180) // 2 purchases per second for 3 minutes ) ) .protocols(httpProtocol) .maxDuration(Duration.ofMinutes(5)) .assertions( global().responseTime().percentile3().lte(500), // 95% of requests < 500ms global().successfulRequests().percent().gte(99.0), // Success rate >= 99% forAll().responseTime().max().lte(2000) // All requests < 2 seconds ); } }

Test Data Files

1. users.csv

username,password,email user1,pass123,[email protected] user2,pass456,[email protected] user3,pass789,[email protected]

2. products.csv

productId,productName 1,Laptop 2,Smartphone 3,Tablet 4,Headphones

3. bodies/login.json

{ "username": "${username}", "password": "${password}" }

4. bodies/create_order.json

{ "productId": "${productId}", "quantity": 1, "customerEmail": "${email}" }

Complex Simulation Examples

Example 1: E-commerce Load Test

public class EcommerceSimulation extends Simulation { HttpProtocolBuilder httpProtocol = http .baseUrl("https://api.ecommerce.com") .acceptHeader("application/json") .contentTypeHeader("application/json") .disableCaching() .shareConnections(); // Feeders FeederBuilder.Batchable<String> userFeeder = csv("data/ecommerce_users.csv").circular(); FeederBuilder.Batchable<String> productFeeder = csv("data/products.csv").random(); FeederBuilder.Batchable<String> searchFeeder = csv("data/search_terms.csv").random(); // Headers with authentication private Map<CharSequence, String> authHeaders(String token) { return Map.of( "Authorization", "Bearer " + token, "Content-Type", "application/json" ); } // Actions ChainBuilder login = exec( http("Login") .post("/api/v1/auth/login") .body(StringBody(""" { "email": "${email}", "password": "${password}" } """)) .check(status().is(200)) .check(jsonPath("$.accessToken").saveAs("accessToken")) .check(jsonPath("$.refreshToken").saveAs("refreshToken")) ); ChainBuilder browseProducts = exec( http("Browse Products") .get("/api/v1/products") .queryParam("page", "0") .queryParam("size", "20") .queryParam("sort", "name,asc") .check(status().is(200)) .check(jsonPath("$.content[0].id").saveAs("firstProductId")) .check(jsonPath("$.content[0].name").saveAs("firstProductName")) ); ChainBuilder searchProducts = exec( http("Search Products") .get("/api/v1/products/search") .queryParam("q", "${searchTerm}") .queryParam("category", "${category}") .check(status().is(200)) .check(jsonPath("$.content[*].id").findRandom().saveAs("searchedProductId")) ); ChainBuilder viewProduct = exec( http("View Product Details") .get("/api/v1/products/#{productId}") .check(status().is(200)) .check(jsonPath("$.price").saveAs("productPrice")) .check(jsonPath("$.inStock").saveAs("inStock")) ); ChainBuilder addToCart = exec( http("Add to Cart") .post("/api/v1/cart/items") .headers(session -> authHeaders(session.getString("accessToken"))) .body(StringBody(""" { "productId": "${productId}", "quantity": 1 } """)) .check(status().is(200)) .check(jsonPath("$.cartItemId").saveAs("cartItemId")) ); ChainBuilder viewCart = exec( http("View Cart") .get("/api/v1/cart") .headers(session -> authHeaders(session.getString("accessToken"))) .check(status().is(200)) .check(jsonPath("$.totalItems").saveAs("cartItemCount")) ); ChainBuilder checkout = exec( http("Checkout") .post("/api/v1/orders") .headers(session -> authHeaders(session.getString("accessToken"))) .body(StringBody(""" { "shippingAddress": { "street": "123 Main St", "city": "Test City", "zipCode": "12345" }, "paymentMethod": "CREDIT_CARD" } """)) .check(status().is(201)) .check(jsonPath("$.orderId").saveAs("orderId")) .check(jsonPath("$.orderStatus").is("PENDING")) ); ChainBuilder getOrderStatus = exec( http("Get Order Status") .get("/api/v1/orders/#{orderId}") .headers(session -> authHeaders(session.getString("accessToken"))) .check(status().is(200)) .check(jsonPath("$.status").saveAs("orderStatus")) ); // Scenarios ScenarioBuilder anonymousBrowser = scenario("Anonymous Browser") .feed(searchFeeder) .exec(browseProducts) .pause(2, 5) .exec(searchProducts) .pause(1, 3) .exec(session -> { String productId = session.getString("searchedProductId"); return session.set("productId", productId); }) .exec(viewProduct) .pause(2); ScenarioBuilder registeredUser = scenario("Registered User") .feed(userFeeder) .feed(productFeeder) .exec(login) .pause(1) .exec(browseProducts) .pause(1, 3) .exec(viewProduct) .pause(1) .exec(addToCart) .pause(1) .exec(viewCart) .pause(2) .doIf(session -> { int itemCount = session.getInt("cartItemCount"); return itemCount > 0; }).then( exec(checkout) .pause(3) .exec(getOrderStatus) ); ScenarioBuilder heavyBuyer = scenario("Heavy Buyer") .feed(userFeeder) .exec(login) .pause(1) .repeat(5, "purchaseCount") { feed(productFeeder) .exec(viewProduct) .exec(addToCart) .pause(1) } .exec(viewCart) .exec(checkout) .exec(getOrderStatus); // Setup { setUp( anonymousBrowser.injectOpen( constantUsersPerSec(5).during(300) // 5 anonymous users/sec for 5 minutes ), registeredUser.injectOpen( rampUsersPerSec(1).to(10).during(180), // Ramp up to 10 registered users/sec constantUsersPerSec(10).during(420) // Maintain for 7 minutes ), heavyBuyer.injectOpen( constantUsersPerSec(2).during(300) // 2 heavy buyers/sec for 5 minutes ) ) .protocols(httpProtocol) .maxDuration(Duration.ofMinutes(10)) .assertions( global().responseTime().percentile3().lte(800), global().successfulRequests().percent().gte(99.5), details("Checkout").responseTime().percentile3().lte(1000), details("Login").responseTime().max().lte(1500) ); } }

Example 2: API Gateway & Microservices Test

public class MicroservicesSimulation extends Simulation { HttpProtocolBuilder httpProtocol = http .baseUrl("https://api-gateway.company.com") .acceptHeader("application/json") .contentTypeHeader("application/json") .disableCaching() .warmUp("https://api-gateway.company.com/health") .connectionHeader("keep-alive"); // Feeders FeederBuilder.Batchable<String> customerFeeder = csv("data/customers.csv").circular(); FeederBuilder.Batchable<String> transactionFeeder = csv("data/transactions.csv").random(); // Headers private Map<CharSequence, String> apiHeaders(String apiKey) { return Map.of( "X-API-Key", apiKey, "X-Request-ID", "#{randomUUID}", "Content-Type", "application/json" ); } // Helper to generate random UUID private static String randomUUID() { return java.util.UUID.randomUUID().toString(); } // Chain builders for different services ChainBuilder getUserProfile = exec(session -> session.set("randomUUID", randomUUID())) .exec( http("Get User Profile") .get("/user-service/v1/users/${userId}") .headers(session -> apiHeaders(session.getString("apiKey"))) .check(status().is(200)) .check(jsonPath("$.user.email").saveAs("userEmail")) ); ChainBuilder getAccountBalance = exec(session -> session.set("randomUUID", randomUUID())) .exec( http("Get Account Balance") .get("/account-service/v1/accounts/${accountId}/balance") .headers(session -> apiHeaders(session.getString("apiKey"))) .check(status().is(200)) .check(jsonPath("$.balance").saveAs("accountBalance")) ); ChainBuilder processPayment = exec(session -> session.set("randomUUID", randomUUID())) .exec( http("Process Payment") .post("/payment-service/v1/payments") .headers(session -> apiHeaders(session.getString("apiKey"))) .body(StringBody(""" { "fromAccount": "${fromAccount}", "toAccount": "${toAccount}", "amount": ${amount}, "currency": "USD" } """)) .check(status().is(201)) .check(jsonPath("$.paymentId").saveAs("paymentId")) .check(jsonPath("$.status").is("COMPLETED")) ); ChainBuilder getTransactionHistory = exec(session -> session.set("randomUUID", randomUUID())) .exec( http("Get Transaction History") .get("/transaction-service/v1/transactions") .queryParam("accountId", "${accountId}") .queryParam("page", "0") .queryParam("size", "10") .headers(session -> apiHeaders(session.getString("apiKey"))) .check(status().is(200)) .check(jsonPath("$.content").exists()) ); ChainBuilder sendNotification = exec(session -> session.set("randomUUID", randomUUID())) .exec( http("Send Notification") .post("/notification-service/v1/notifications") .headers(session -> apiHeaders(session.getString("apiKey"))) .body(StringBody(""" { "userId": "${userId}", "type": "TRANSACTION_ALERT", "message": "Payment of ${amount} processed successfully", "channel": "EMAIL" } """)) .check(status().is(202)) ); // Complex business workflow ChainBuilder moneyTransferWorkflow = exec(session -> session.set("randomUUID", randomUUID())) .exec(getUserProfile) .pause(1) .exec(getAccountBalance) .pause(1) .doIf(session -> { double balance = session.getDouble("accountBalance"); double amount = session.getDouble("amount"); return balance >= amount; }).then( exec(processPayment) .pause(2) .exec(sendNotification) .exec(getTransactionHistory) ).orElse( exec( http("Insufficient Funds") .post("/notification-service/v1/notifications") .headers(session -> apiHeaders(session.getString("apiKey"))) .body(StringBody(""" { "userId": "${userId}", "type": "LOW_BALANCE_ALERT", "message": "Insufficient funds for transfer", "channel": "SMS" } """)) .check(status().is(202)) ) ); // Scenarios ScenarioBuilder userProfileReaders = scenario("Profile Readers") .feed(customerFeeder) .during(300).on( // Run for 5 minutes randomSwitch().on( Choice.withWeight(70, exec(getUserProfile).pause(2, 5)), Choice.withWeight(30, exec(getAccountBalance).pause(1, 3)) ) ); ScenarioBuilder transactionProcessors = scenario("Transaction Processors") .feed(customerFeeder) .feed(transactionFeeder) .exec(moneyTransferWorkflow) .pause(5, 10); ScenarioBuilder mixedWorkload = scenario("Mixed Workload") .feed(customerFeeder) .feed(transactionFeeder) .forever().on( randomSwitch().on( Choice.withWeight(40, exec(getUserProfile)), Choice.withWeight(25, exec(getAccountBalance)), Choice.withWeight(20, exec(getTransactionHistory)), Choice.withWeight(15, exec(moneyTransferWorkflow)) ).pause(1, 5) ); // Setup with throttling { setUp( userProfileReaders.injectOpen( rampUsers(100).during(60) // 100 users over 1 minute ), transactionProcessors.injectOpen( constantUsersPerSec(5).during(300) // 5 transactions/sec for 5 minutes ), mixedWorkload.injectOpen( rampUsersPerSec(1).to(20).during(120), // Ramp up to 20 requests/sec constantUsersPerSec(20).during(480) // Maintain for 8 minutes ) ) .protocols(httpProtocol) .throttle( reachRps(100).in(60), // Reach 100 RPS in 1 minute holdFor(480), // Hold for 8 minutes jumpToRps(50), // Jump to 50 RPS holdFor(120) // Hold for 2 minutes ) .maxDuration(Duration.ofMinutes(15)) .assertions( global().responseTime().percentile3().lte(300), global().successfulRequests().percent().gte(99.9), details("Process Payment").responseTime().mean().lte(500), details("Get User Profile").responseTime().percentile4().lte(200) ); } }

Advanced Features

1. Custom Checks and Assertions

public class AdvancedChecksSimulation extends Simulation { HttpProtocolBuilder httpProtocol = http .baseUrl("https://api.example.com") .acceptHeader("application/json"); // Custom response time check ChainBuilder checkResponseTime = exec( http("API Call") .get("/api/data") .check( status().is(200), responseTimeInMillis().lt(1000).saveAs("responseTime"), bodyString().transform(body -> { // Custom validation logic if (body.contains("error")) { throw new RuntimeException("Response contains error"); } return body; }) ) ); // JSON schema validation ChainBuilder validateJsonSchema = exec( http("Get Structured Data") .get("/api/structured") .check( jsonPath("$.id").is("#{expectedId}"), jsonPath("$.name").notNull(), jsonPath("$.items").count().gte(1), jsonPath("$.metadata.timestamp").ofType(Long.class).gt(0) ) ); // Session validation ChainBuilder validateSessionData = exec( http("Get Data") .get("/api/data") .check( jsonPath("$.results[*].id").findAll().saveAs("allIds") ) ) .exec(session -> { List<String> allIds = session.getList("allIds"); if (allIds.isEmpty()) { throw new RuntimeException("No IDs found in response"); } return session; }); ScenarioBuilder scn = scenario("Advanced Checks") .exec(checkResponseTime) .exec(validateJsonSchema) .exec(validateSessionData); { setUp( scn.injectOpen(atOnceUsers(10)) ).protocols(httpProtocol); } }

2. Data-Driven Testing with Feeds

public class DataDrivenSimulation extends Simulation { HttpProtocolBuilder httpProtocol = http .baseUrl("https://jsonplaceholder.typicode.com"); // Different feeder types FeederBuilder.Batchable<String> csvFeeder = csv("data/test_data.csv").random(); FeederBuilder.Batchable<String> jsonFeeder = jsonFile("data/test_data.json").random(); FeederBuilder.Batchable<String> ssvFeeder = ssv("data/test_data.ssv").circular(); // Custom feeder from code Iterator<Map<String, Object>> customFeeder = new Iterator<Map<String, Object>>() { private int counter = 0; @Override public boolean hasNext() { return true; } @Override public Map<String, Object> next() { return Map.of( "userId", counter++, "username", "user" + counter, "email", "user" + counter + "@test.com" ); } }; ChainBuilder createUser = feed(customFeeder) .exec( http("Create User #{userId}") .post("/users") .body(StringBody(""" { "name": "${username}", "email": "${email}", "username": "user#{userId}" } """)) .check(status().is(201)) .check(jsonPath("$.id").saveAs("newUserId")) ); ChainBuilder updateUser = exec( http("Update User") .put("/users/#{newUserId}") .body(StringBody(""" { "name": "Updated User", "email": "[email protected]" } """)) .check(status().is(200)) ); ScenarioBuilder scn = scenario("Data Driven Test") .exec(createUser) .pause(1) .exec(updateUser); { setUp( scn.injectOpen( constantUsersPerSec(2).during(60) // 2 users/sec for 1 minute ) ).protocols(httpProtocol); } }

Running Gatling Tests

1. Maven Commands

# Run all Gatling tests mvn gatling:test # Run specific simulation mvn gatling:test -Dgatling.simulationClass=BasicSimulation # Run with custom properties mvn gatling:test -DbaseUrl=https://staging.example.com -Dusers=100

2. Gradle Commands

# Run all Gatling tests ./gradlew gatlingRun # Run specific simulation ./gradlew gatlingRun-AdvancedApiSimulation # Run with custom properties ./gradlew gatlingRun -DbaseUrl=https://staging.example.com

3. Programmatic Execution

public class GatlingRunner { public static void main(String[] args) { Engine .engine() .resourcesDirectory(IDEPathHelper.gradleResourcesDirectory.toString()) .resultsDirectory(IDEPathHelper.resultsDirectory.toString()) .reportsDirectory(IDEPathHelper.reportsDirectory.toString()) .run(); } }

Results Analysis

Gatling generates comprehensive HTML reports including:

  • Response Time Distribution
  • Requests per Second
  • Active Users Over Time
  • Error Statistics
  • Percentile Response Times

Best Practices

  1. Start Simple: Begin with basic scenarios and gradually add complexity
  2. Use Realistic Data: Feed realistic test data that matches production
  3. Simulate Real User Behavior: Include think times and random pauses
  4. Test Gradually: Start with low load and gradually increase
  5. Monitor Application: Correlate Gatling results with application metrics
  6. Use Assertions: Define performance requirements in your simulations
  7. Run Consistently: Use the same environment for comparable results
  8. Document Scenarios: Clearly document what each scenario represents

This comprehensive Gatling setup provides robust performance testing capabilities for Java applications, from simple API tests to complex microservices architectures.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper