Documentation Index
Fetch the complete documentation index at: https://mintlify.com/gatling/gatling.io-doc/llms.txt
Use this file to discover all available pages before exploring further.
Feeders are how Gatling injects dynamic data into each virtual user’s session. While basic CSV and JSON feeders cover the majority of use cases, large-scale and production-representative simulations often require more sophisticated patterns: grouping records so a single virtual user always receives a coherent set of related data, reading feeder files from AWS S3 to keep sensitive data out of your repository, or fetching credentials at runtime from AWS Secrets Manager. This guide covers all of these advanced patterns with concrete implementation examples.
Grouped Feeder Records
A common challenge arises when your feeder data has a one-to-many relationship — for example, a CSV where multiple URLs belong to the same user, and you want each virtual user to receive only their own URLs.
The Problem
username,url
user1,/account/profile
user1,/account/orders
user2,/settings
user2,/billing
With a standard feeder, user1 might receive /settings and user2 might receive /account/profile — the association between username and URL is lost.
Solution: readRecords with In-Memory Grouping
Use readRecords to load all CSV records into memory at simulation startup, then group them by user before building a custom iterator.
import io.gatling.javaapi.core.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import java.util.*;
import java.util.stream.*;
public class GroupedFeederSimulation extends Simulation {
// Load all records from the CSV feeder file
private static final List<Map<String, Object>> ALL_RECORDS =
csv("data/users_urls.csv").readRecords();
// Group records by username
private static final Map<String, List<Map<String, Object>>> GROUPED =
ALL_RECORDS.stream()
.collect(Collectors.groupingBy(r -> (String) r.get("username")));
// Build an iterator that yields one group per virtual user
private static final Iterator<Map<String, Object>> groupedFeeder =
GROUPED.entrySet().stream()
.map(e -> Map.<String, Object>of(
"username", e.getKey(),
"urls", e.getValue().stream()
.map(r -> r.get("url"))
.collect(Collectors.toList())
))
.collect(Collectors.toList())
.iterator();
ScenarioBuilder scn = scenario("Grouped Feeder")
.feed(groupedFeeder)
.foreach("#{urls}", "url").on(
exec(http("Visit URL").get("#{url}"))
);
{
setUp(scn.injectOpen(atOnceUsers(2)))
.protocols(httpProtocol);
}
}
readRecords loads the entire file into the JVM heap once, at simulation initialisation. It is ideal for files up to a few hundred thousand rows. For very large datasets, consider streaming from S3 instead (see below).
Feeder Strategies
Gatling provides four built-in strategies for how records are consumed:
| Strategy | Behaviour | Best for |
|---|
.queue() | Records served in order; simulation stops when exhausted | One-time credential pools |
.random() | Random record each time, with replacement | Product catalogues, broad data variety |
.shuffle() | All records shuffled, then served in order | Randomised but fully covered data sets |
.circular() | Loops back to the first record when the last is reached | Long-running simulations with small data files |
AWS S3 Bucket Feeders
When feeder files are large, change frequently, or contain sensitive data, storing them in an AWS S3 bucket is a better choice than committing them to your repository.
S3 feeders work with private locations on Gatling Enterprise Edition and local test execution. They do not work with managed (cloud) locations on Gatling Enterprise Edition.
IAM Configuration
Grant your load generators read access to the S3 bucket via an IAM instance profile policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowFeederRead",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-feeders-bucket/*"
}
]
}
Additionally, the Gatling Enterprise control plane needs permission to pass this profile to load generators:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": ["iam:PassRole"],
"Effect": "Allow",
"Resource": ["arn:aws:iam::{Account}:role/{RoleNameWithPath}"]
}
]
}
Add the AWS SDK Dependency
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.27.0</version>
</dependency>
Implementation
The recommended pattern downloads the feeder file from S3 at simulation startup, uses it locally during the run, then deletes the temporary file when the simulation completes.
import io.gatling.javaapi.core.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import java.io.IOException;
import java.nio.file.*;
public class S3FeederSimulation extends Simulation {
private final String bucketName = "my-feeders-bucket";
private final String objectKey = "data/users.csv";
private final Path feederFile = loadFeeder();
private Path loadFeeder() {
try (S3Client s3 = S3Client.create()) {
Path tmp = Files.createTempFile("gatling-feeder-", ".csv");
tmp.toFile().deleteOnExit();
try (ResponseInputStream<GetObjectResponse> stream = s3.getObject(
GetObjectRequest.builder().bucket(bucketName).key(objectKey).build())) {
Files.copy(stream, tmp, StandardCopyOption.REPLACE_EXISTING);
}
return tmp;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// Use the downloaded file as a circular feeder
private final FeederBuilder<String> usersFeeder =
csv(feederFile.toFile().getAbsolutePath()).circular();
ScenarioBuilder scn = scenario("S3 Feeder Test")
.feed(usersFeeder)
.exec(http("Authenticated Request")
.get("/api/protected")
.header("X-User", "#{username}"));
{
setUp(scn.injectOpen(rampUsers(100).during(60)))
.protocols(httpProtocol);
}
}
AWS Secrets Manager Integration
For credentials and API keys that must never be stored in files, retrieve them securely from AWS Secrets Manager inside the simulation’s initialisation block.
This integration requires Gatling Enterprise Edition with private locations and the AWS SDK for Java 2.x. The secret is retrieved only once per load generator spawn, not per virtual user.
IAM Policy for Secrets Manager
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:{region}:{account-id}:secret:{secret-name}"
}
]
}
For batch retrieval, add secretsmanager:BatchGetSecretValue to the policy.
Reading a Secret at Simulation Startup
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
public class SecretsSimulation extends Simulation {
// Retrieve the secret once during class initialisation
private static final String API_KEY = fetchSecret("prod/myapp/api-key");
private static String fetchSecret(String secretName) {
try (SecretsManagerClient client = SecretsManagerClient.create()) {
GetSecretValueResponse response = client.getSecretValue(
GetSecretValueRequest.builder().secretId(secretName).build()
);
return response.secretString();
}
}
ScenarioBuilder scn = scenario("Secrets Test")
.exec(http("Protected Endpoint")
.get("/api/data")
.header("Authorization", "Bearer " + API_KEY));
{
setUp(scn.injectOpen(atOnceUsers(10)))
.protocols(httpProtocol);
}
}
Custom Feeder with DataFaker
When you need synthetic but realistic data — names, emails, addresses — build a custom iterator using the DataFaker library.
import net.datafaker.Faker;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Stream;
Faker faker = new Faker();
Iterator<Map<String, Object>> customFeeder =
Stream.generate((Supplier<Map<String, Object>>) () ->
Map.of(
"firstName", faker.name().firstName(),
"lastName", faker.name().lastName(),
"email", faker.internet().emailAddress()
)
).iterator();
ScenarioBuilder scn = scenario("Custom Feeder")
.feed(customFeeder)
.exec(http("Register User")
.post("/api/users")
.body(StringBody(
"{\"firstName\":\"#{firstName}\"," +
"\"lastName\":\"#{lastName}\"," +
"\"email\":\"#{email}\"}"
)).asJson()
.check(status().is(201)));
Stream.generate creates an infinite lazy stream, so the feeder never exhausts regardless of how many virtual users your simulation injects. This is the recommended approach for generated data.