Skip to main content

EXTENSIONS

Performance Testing

Reuse your Karate functional tests as Gatling performance tests. Validate API correctness under load with full response assertions, not just status codes. Write load models in Java or Scala while keeping all test logic in Karate.

On this page:

Quick Start

Maven Setup

Add Gatling support to your existing Karate project:

pom.xml
<properties>
<karate.version>1.5.2</karate.version>
<gatling.plugin.version>4.3.7</gatling.plugin.version>
</properties>

<dependencies>
<dependency>
<groupId>io.karatelabs</groupId>
<artifactId>karate-gatling</artifactId>
<version>${karate.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.5.6</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-Jbackend:GenBCode</arg>
<arg>-Jdelambdafy:method</arg>
<arg>-target:jvm-1.8</arg>
<arg>-deprecation</arg>
<arg>-feature</arg>
<arg>-unchecked</arg>
<arg>-language:implicitConversions</arg>
<arg>-language:postfixOps</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>${gatling.plugin.version}</version>
<configuration>
<simulationsFolder>src/test/java</simulationsFolder>
<includes>
<include>perf.UsersSimulation</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>

Run performance tests:

Shell
# Compile and run
mvn clean test-compile gatling:test

# Run specific simulation
mvn clean test-compile gatling:test -Dgatling.simulationClass=perf.UsersSimulation

Gradle Setup

build.gradle
plugins {
id 'scala'
}

dependencies {
testImplementation "io.karatelabs:karate-gatling:1.5.2"
}

task gatlingRun(type: JavaExec) {
classpath = sourceSets.test.runtimeClasspath
mainClass = 'io.gatling.app.Gatling'
args = ['-s', 'perf.UsersSimulation', '-rf', 'build/reports/gatling']
}

Ensure all *.feature files are copied to the resources folder when you build.

Demo Video

Watch the Karate Gatling webinar for a complete walkthrough of performance testing with Karate.

Gatling now supports Java-based simulations, eliminating the need to learn Scala. This is the recommended approach for new projects.

src/test/java/perf/UsersSimulation.java
package perf;

import com.intuit.karate.gatling.javaapi.KarateProtocolBuilder;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;

import static io.gatling.javaapi.core.CoreDsl.*;
import static com.intuit.karate.gatling.javaapi.KarateDsl.*;

public class UsersSimulation extends Simulation {

public UsersSimulation() {

KarateProtocolBuilder protocol = karateProtocol(
uri("/users/{id}").nil(),
uri("/users").pauseFor(method("get", 15), method("post", 25))
);

protocol.runner.karateEnv("perf");

ScenarioBuilder getUsers = scenario("get users")
.exec(karateFeature("classpath:perf/get-users.feature"));

ScenarioBuilder createUser = scenario("create user")
.exec(karateFeature("classpath:perf/create-user.feature"));

setUp(
getUsers.injectOpen(rampUsers(10).during(5)).protocols(protocol),
createUser.injectOpen(rampUsers(5).during(5)).protocols(protocol)
);
}
}

The feature file contains your test logic with full assertions:

src/test/java/perf/get-users.feature
Feature: Get users performance test

Scenario: Get all users
Given url 'https://jsonplaceholder.typicode.com'
And path 'users'
When method get
Then status 200
And match response == '#[10]'
And match each response contains { id: '#number', name: '#string', email: '#string' }

Scala DSL

For teams already using Scala or maintaining existing Scala simulations:

src/test/scala/perf/UsersSimulation.scala
package perf

import com.intuit.karate.gatling.PreDef._
import io.gatling.core.Predef._
import scala.concurrent.duration._

class UsersSimulation extends Simulation {

val protocol = karateProtocol(
"/users/{id}" -> Nil,
"/users" -> pauseFor("get" -> 15, "post" -> 25)
)

protocol.runner.karateEnv("perf")

val getUsers = scenario("get users")
.exec(karateFeature("classpath:perf/get-users.feature"))

val createUser = scenario("create user")
.exec(karateFeature("classpath:perf/create-user.feature"))

setUp(
getUsers.inject(rampUsers(10) during (5 seconds)).protocols(protocol),
createUser.inject(rampUsers(5) during (5 seconds)).protocols(protocol)
)
}

karateProtocol()

The protocol configuration is required because Karate makes HTTP requests while Gatling manages timing and threads. Declare URL patterns so requests aggregate correctly in Gatling reports.

Java
KarateProtocolBuilder protocol = karateProtocol(
uri("/users/{id}").nil(), // No pause for this pattern
uri("/users").pauseFor(method("get", 15), method("post", 25)), // 15ms for GET, 25ms for POST
uri("/orders/{orderId}/items/{itemId}").nil() // Path parameters use {name}
);

Without this configuration, each unique URL (e.g., /users/1, /users/2) would appear as a separate entry in reports instead of aggregating under /users/{id}.

pauseFor()

Set pause times (in milliseconds) per URL pattern and HTTP method. The pause is applied before the matching request. Use nil() (Java) or Nil (Scala) for zero pause on all methods.

Pause Recommendation

Set pauses to 0 unless you need to artificially limit requests per second. For realistic user think time, use karate.pause() within your feature files instead.

nameResolver

For GraphQL and SOAP APIs where the URI stays constant but the payload changes, use nameResolver to customize how requests are named in reports:

Java
protocol.nameResolver = (req, ctx) -> req.getHeader("karate-name");

In your feature file, set a custom header to control the report name:

Gherkin
Feature: GraphQL performance test

Scenario: Get user by ID
Given url graphqlUrl
And header karate-name = 'graphql-getUser'
And request { query: '{ user(id: 1) { name email } }' }
When method post
Then status 200

If nameResolver returns null, Karate falls back to the default URL-based naming.

runner Configuration

Access Runner.Builder methods for custom configuration:

Java
// Set Karate environment (uses karate-config-perf.js)
protocol.runner.karateEnv("perf");

// Set config directory
protocol.runner.configDir("src/test/resources");

// Set system properties
protocol.runner.systemProperty("api.baseUrl", "https://perf-api.example.com");
Environment Configuration

Setting karateEnv("perf") loads karate-config-perf.js in addition to karate-config.js. Alternatively, pass -Dkarate.env=perf on the command line.

karateFeature()

Execute entire Karate features as performance test flows:

Java
ScenarioBuilder scenario = scenario("user flow")
.exec(karateFeature("classpath:perf/user-flow.feature"));

Multiple features can run concurrently with different load profiles:

Java
setUp(
browsing.injectOpen(constantUsersPerSec(7).during(300)).protocols(protocol),
purchasing.injectOpen(constantUsersPerSec(2).during(300)).protocols(protocol),
admin.injectOpen(constantUsersPerSec(1).during(300)).protocols(protocol)
);

Silent Execution

For warm-up phases that should not count toward statistics:

Java
ScenarioBuilder warmup = scenario("warmup")
.exec(karateFeature("classpath:perf/warmup.feature").silent());

Tag Selectors

Select specific scenarios from a feature file by appending tags:

Java
// Run scenario with @name=delete tag
karateFeature("classpath:perf/users.feature@name=delete")

// Multiple tags as separate arguments (AND logic)
karateFeature("classpath:perf/users.feature", "@name=delete")

// OR logic with comma
karateFeature("classpath:perf/users.feature", "@smoke,@critical")

// AND logic with separate strings
karateFeature("classpath:perf/users.feature", "@smoke", "@fast")

// Exclude tags
karateFeature("classpath:perf/users.feature", "~@slow")

This allows reusing functional test scenarios for performance testing without modification.

Tag Inheritance

Tags set on protocol.runner.tags() are inherited by all karateFeature() calls. You can add more tags per feature but cannot remove inherited ones.

Data Flow

Gatling Session Access

Access Gatling session data in Karate via the __gatling namespace:

Gherkin
Feature: Access Gatling data

Scenario: Use Gatling user ID
* print 'Gatling userId:', __gatling.userId
Given url baseUrl
And path 'users', __gatling.userId
When method get
Then status 200

Karate Variables in Gatling

Variables created in a karateFeature() execution are added to the Gatling session:

Java
ScenarioBuilder create = scenario("create")
.exec(karateFeature("classpath:perf/create-user.feature"))
.exec(session -> {
System.out.println("Created user ID: " + session.getString("userId"));
return session;
});

karateSet()

Inject Gatling session data into Karate variables:

Java
ScenarioBuilder scenario = scenario("with data")
.exec(karateSet("username", session -> "user_" + session.userId()))
.exec(karateFeature("classpath:perf/user-actions.feature"));

Feeders

Use Gatling feeders to supply test data:

Java
import java.util.*;

public class TestData {
private static final AtomicInteger counter = new AtomicInteger();
private static final List<String> names = Arrays.asList("Alice", "Bob", "Carol", "Dave");

public static String getNextName() {
return names.get(counter.getAndIncrement() % names.size());
}
}
Scala
val feeder = Iterator.continually(Map(
"userName" -> TestData.getNextName(),
"timestamp" -> System.currentTimeMillis()
))

val scenario = scenario("with feeder")
.feed(feeder)
.exec(karateFeature("classpath:perf/create-user.feature"))

Access feeder values in your feature:

Gherkin
Feature: Create user with feeder data

Scenario: Create user
* print 'Creating user:', __gatling.userName
Given url baseUrl
And path 'users'
And request { name: '#(__gatling.userName)' }
When method post
Then status 201

Chaining Scenarios

Variables flow between features within the same Gatling scenario:

Java
ScenarioBuilder flow = scenario("user flow")
.exec(karateFeature("classpath:perf/create-user.feature")) // Creates userId
.exec(karateFeature("classpath:perf/update-user.feature")) // Uses userId from above
.exec(karateFeature("classpath:perf/delete-user.feature")); // Uses userId from above

karate.callSingle()

Run setup code once across all threads (e.g., authentication):

karate-config-perf.js
function fn() {
var config = { baseUrl: 'https://api.example.com' };

// Runs once globally, even with parallel threads
var auth = karate.callSingle('classpath:auth/get-token.feature');
config.authToken = auth.token;

return config;
}
Thread Locking

callSingle and callonce lock all threads during execution, which may impact Gatling performance. For high-throughput tests, prefer using feeders for test data.

Detecting Gatling at Runtime

Write features that work both in functional tests and performance tests:

Gherkin
Feature: Dual-purpose test

Scenario: Get user
# Use feeder value if running in Gatling, otherwise use default
* def userName = karate.get('__gatling.userName', 'TestUser')
Given url baseUrl
And path 'users'
And param name = userName
When method get
Then status 200

Think Time

Use karate.pause() for non-blocking pauses that work correctly with Gatling:

Gherkin
Feature: Realistic user flow

Scenario: Shopping journey
# Browse products
Given url baseUrl
And path 'products'
When method get
Then status 200

# User thinks for 2 seconds
* karate.pause(2000)

# View product details
Given path 'products', response[0].id
When method get
Then status 200

# User thinks for 3 seconds before purchase
* karate.pause(3000)

# Add to cart
Given path 'cart'
And request { productId: '#(response.id)', quantity: 1 }
When method post
Then status 201
Never Use Thread.sleep()

Thread.sleep() blocks threads and interferes with Gatling's non-blocking architecture. Always use karate.pause() for think time in performance tests.

By default, karate.pause() only works during Gatling execution. To enable it in normal test runs:

Gherkin
* configure pauseIfNotPerf = true

configure localAddress

Bind HTTP requests to a specific local IP address to avoid rate limiting:

Gherkin
Feature: Distributed load test

Scenario: Request from specific IP
* configure localAddress = '192.168.1.100'
Given url baseUrl
And path 'users'
When method get
Then status 200

For round-robin IP selection:

Gherkin
* if (__gatling) karate.configure('localAddress', IpPool.getNextIp())

Custom Java Code

Test non-HTTP protocols (gRPC, databases, message queues) with full Gatling reporting using PerfContext:

src/test/java/perf/CustomProtocol.java
package perf;

import com.intuit.karate.PerfContext;
import java.util.Collections;
import java.util.Map;

public class CustomProtocol {

public static Map<String, Object> callDatabase(Map<String, Object> request, PerfContext context) {
long startTime = System.currentTimeMillis();

// Your custom code here (database call, gRPC, etc.)
String query = (String) request.get("query");
// ... execute query ...

long endTime = System.currentTimeMillis();

// Report to Gatling
context.capturePerfEvent("db-query-" + query.hashCode(), startTime, endTime);

return Collections.singletonMap("success", true);
}
}

Call from your feature file:

Gherkin
Feature: Database performance test

Background:
* def CustomProtocol = Java.type('perf.CustomProtocol')

Scenario: Query performance
* def request = { query: 'SELECT * FROM users WHERE active = true' }
* def result = CustomProtocol.callDatabase(request, karate)
* match result == { success: true }

The karate object implements PerfContext, so pass it directly to your Java methods. Test failures are automatically linked to the captured performance event.

Use Cases

Custom Java integration enables performance testing for:

  • Database queries
  • gRPC services
  • Message queues (Kafka, RabbitMQ)
  • Proprietary protocols
  • Any Java-callable code

Configuration

Maven Profile for Isolation

Keep Gatling dependencies separate to avoid conflicts with your main test framework:

pom.xml
<profiles>
<profile>
<id>gatling</id>
<dependencies>
<dependency>
<groupId>io.karatelabs</groupId>
<artifactId>karate-gatling</artifactId>
<version>${karate.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.5.6</version>
<executions>
<execution>
<goals><goal>testCompile</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>${gatling.plugin.version}</version>
<configuration>
<simulationsFolder>src/test/java</simulationsFolder>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

Run with the profile:

Shell
mvn clean test -P gatling

Thread Pool Configuration

By default, Karate-Gatling supports approximately 30 RPS. For higher throughput, create gatling-akka.conf in src/test/resources:

src/test/resources/gatling-akka.conf
akka {
actor {
default-dispatcher {
type = Dispatcher
executor = "thread-pool-executor"
thread-pool-executor {
fixed-pool-size = 100
}
throughput = 1
}
}
}

Adjust fixed-pool-size based on your target RPS and available system resources.

Logging

For performance tests, reduce logging overhead in logback-test.xml:

src/test/resources/logback-test.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<immediateFlush>false</immediateFlush>
</appender>

<logger name="com.intuit.karate" level="WARN"/>

<root level="WARN">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

You can also suppress logging in karate-config-perf.js:

karate-config-perf.js
function fn() {
karate.configure('logPrettyRequest', false);
karate.configure('logPrettyResponse', false);
karate.configure('printEnabled', false);
return { baseUrl: 'https://api.example.com' };
}

Limitations

LimitationDetails
Throttle not supportedGatling's throttle syntax is not available. Use pauseFor() or karate.pause() instead.
Default RPS ~30Increase thread pool size for higher throughput. See Thread Pool Configuration.
Non-blocking pause requiredNever use Thread.sleep(). Use karate.pause() for think time.

Distributed Testing

For large-scale load tests across multiple machines or Docker containers, see the Distributed Testing Wiki.

Troubleshooting

ProblemCauseSolution
Low RPSDefault thread pool too smallIncrease fixed-pool-size in gatling-akka.conf
Test freezingLong response times blocking threadsIncrease thread pool, check readTimeout
OutOfMemoryErrorToo much test data in memoryOptimize data usage, increase heap size
Connection timeoutsNetwork issues or server overloadIncrease connectTimeout, check target server
Requests not aggregatingMissing karateProtocol() patternsAdd URL patterns with path parameters
Variables not flowingScenario isolationUse chaining within same Gatling scenario

Resources

Next Steps