QA How-To
Gatling Feeders Tutorial: CSV, JSON and Custom Feeders
Gatling feeders tutorial for Java: build CSV, JSON, and custom feeders, verify queue exhaustion and circular reuse, and run a local load test step by step.
19 min read | 3,196 words
TL;DR
Use Gatling's csv and jsonFile builders for file-backed records and an Iterator<Map<String, Object>> for generated records. Feed each source before the HTTP actions, choose queue or circular based on whether values may repeat, and verify both successful requests and intentional queue exhaustion.
Key Takeaways
- Place CSV and JSON files under src/test/resources and reference them by classpath path.
- Use queue for unique finite data and circular only where record reuse is safe.
- A custom Java feeder can implement Iterator<Map<String, Object>> with thread-safe generated values.
- Count feed executions, not just virtual users, when sizing a finite dataset.
- Verify feeder values at the target before interpreting Gatling latency reports.
- Run a small local check before replacing the target with a staging service.
This Gatling Feeders Tutorial builds one Java load test that draws search terms from CSV, item details from JSON, and unique request IDs from a custom feeder. You will run it against a small local HTTP service, prove that each record reaches a request, and see exactly what happens when a finite feeder runs out.
A feeder is a source of maps. Each time a virtual user reaches .feed(...), Gatling takes one map and adds its keys to that user's Session. Later HTTP actions resolve #{term}, #{itemId}, and #{requestId} from those Session attributes. Keep that sequence in mind: consuming data and making a request are separate actions.
What You Will Build
- A local catalog stub with
/searchand/items/{id}endpoints that rejects missing feeder values. - A three-row CSV file for search terms and a two-record JSON array for item data.
- One Gatling simulation that feeds CSV, JSON, and generated request IDs into the same virtual user.
- A queue run with three users, an intentional exhaustion check, and a circular run with reuse enabled.
- A report-reading checklist that distinguishes data errors from HTTP performance results.
Prerequisites
Use JDK 17, Git, and a shell with curl. Gatling's current Java tutorial supports OpenJDK 11 through 25 and targets Java 17; this article uses 17 so Java source-file launch and the simulation agree. Use the Maven Wrapper from Gatling's official Java Maven demo, or Maven 3.6.3 or newer if your team does not use wrappers. Use the Gatling version declared by the cloned starter project, and confirm it matches the Java DSL APIs shown here. Do not paste a guessed Gatling dependency or plugin version into pom.xml.
java -version
curl --version
git --version
The first command should report Java 17. The other two should exit successfully. The official Java installation guide documents the supported runtimes and starter layout. You need permission to run a low-volume test against your chosen target. Here the target listens only on 127.0.0.1, so no external test service is involved.
On Windows, use PowerShell equivalents for file creation and mvnw.cmd for Maven commands. The shell blocks below target macOS or Linux. Start all Gatling commands from the cloned project's root; resource paths are relative to its test classpath, not to whatever directory your terminal happens to be in.
Step 1: Start from the Java Maven Demo
Clone the official demo, then check its wrapper before editing a simulation. The project provides the compatible Gatling dependency, plugin, and Maven configuration together. Keep those declared versions as a set. The tutorial class will live in the example package, and the data files will live below src/test/resources/data.
git clone https://github.com/gatling/gatling-maven-plugin-demo-java.git
cd gatling-maven-plugin-demo-java
./mvnw -version
mkdir -p src/test/java/example src/test/resources/data
Verify Step 1: ./mvnw -version must finish successfully and show that Maven is using JDK 17. test -f pom.xml && test -d src/test/resources/data should exit with status zero. If your environment blocks GitHub, download the same official starter archive and run the remaining commands from its project root. Do not mix a different starter's pom.xml with these source files; that is an easy way to create a Java DSL and plugin mismatch.
Step 2: Run a Local Target That Checks the Fed Values
Create MockCatalog.java in the project root. JDK 17 can run a single Java source file directly. This stub returns 200 OK for a search only when it receives both a nonempty term query parameter and X-Request-Id header. The item endpoint accepts an item ID in the path and a nonempty category query parameter. A 400 response therefore points to a missing or malformed feeder value rather than a silent false positive.
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
public class MockCatalog {
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(
new InetSocketAddress("127.0.0.1", 8089), 0);
server.createContext("/search", exchange -> {
String query = exchange.getRequestURI().getRawQuery();
String requestId = exchange.getRequestHeaders().getFirst("X-Request-Id");
boolean valid = query != null && query.startsWith("term=")
&& query.length() > 5 && requestId != null && !requestId.isBlank();
respond(exchange, valid ? 200 : 400);
});
server.createContext("/items", exchange -> {
String path = exchange.getRequestURI().getPath();
String query = exchange.getRequestURI().getRawQuery();
boolean valid = path.matches("/items/[0-9]+")
&& query != null && query.startsWith("category=")
&& query.length() > 9;
respond(exchange, valid ? 200 : 400);
});
server.start();
System.out.println("Mock catalog listening on 127.0.0.1:8089");
}
private static void respond(HttpExchange exchange, int status)
throws IOException {
byte[] bytes = "OK".getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(status, bytes.length);
try (var output = exchange.getResponseBody()) {
output.write(bytes);
}
}
}
Save the block as MockCatalog.java, then start it in a separate terminal with java MockCatalog.java. Keep that terminal open. The stub uses the JDK's HttpServer; it is a teaching target, not an application benchmark. Its immediate response gives you a known-good path through the feeder plumbing.
Verify Step 2: Run curl -i 'http://127.0.0.1:8089/search?term=shoes' -H 'X-Request-Id: check-1' and curl -i 'http://127.0.0.1:8089/items/101?category=footwear'. Both should show HTTP/1.1 200 OK and OK in the body. Run curl -i 'http://127.0.0.1:8089/search?term=shoes' without the header; it should return 400. If port 8089 is occupied, change it in both MockCatalog.java and the simulation shown in Step 5.
Step 3: Add CSV Records for Search Terms
Create src/test/resources/data/searches.csv. The first row names Session keys, so its spelling is part of the simulation contract. term is intentionally simple; a search phrase containing a comma would need correct CSV quoting. These three values make the queue's capacity easy to verify.
term
shoes
jackets
socks
The simulation will use csv("data/searches.csv").queue(). Gatling locates that path on the test classpath. The actual file belongs under src/test/resources, with no src/test/resources prefix in the DSL call. Current Gatling versions do not resolve a relative project file path as a feeder resource. The queue strategy gives the first three feed calls distinct rows and fails when a fourth feed call asks for more. That is appropriate for unique accounts or identifiers, but not for search terms you intend to reuse indefinitely.
Verify Step 3: Run test "$(wc -l < src/test/resources/data/searches.csv | tr -d ' ')" = 4 and head -n 1 src/test/resources/data/searches.csv. The first command should exit zero, and the second should print term. The count includes the header and three records. If you create the file in an editor, ensure it ends with a newline. A spreadsheet export can add a byte-order mark or change delimiters; inspect the first line in a plain text editor if Gatling later reports a missing term attribute.
Step 4: Add a JSON Array for Item Details
Create src/test/resources/data/items.json. Gatling's jsonFile feeder expects the root to be an array of objects. Each object becomes one map. The itemId values below are numbers, while category values are strings; Gatling can interpolate both into HTTP request paths or query parameters.
[
{ "itemId": 101, "category": "footwear" },
{ "itemId": 202, "category": "outerwear" }
]
The simulation will call jsonFile("data/items.json").circular(). There are only two item records but three users. Circular consumption produces item IDs 101, 202, then 101 again. That reuse is deliberate: this stub reads items and does not mutate them. If the endpoint created an item or reserved inventory, recycling an ID could change the workload and cause collisions. In that case, use enough unique rows with queue() or generate new IDs.
Verify Step 4: Run python3 -m json.tool src/test/resources/data/items.json >/dev/null and confirm it exits zero. Then run test "$(python3 -c 'import json; print(len(json.load(open("src/test/resources/data/items.json"))))')" = 2. The second command should also exit zero. These checks prove syntactic JSON and the intended array length before Gatling parses it. A JSON object at the root can be valid JSON but is the wrong feeder shape, so look for the outer square brackets.
Step 5: Build the Simulation and a Custom Feeder
Save this complete class as src/test/java/example/FeederTutorialSimulation.java. It declares the CSV and JSON builders once when Gatling creates the simulation. The custom feeder is a Java Iterator<Map<String, Object>>. Its AtomicLong produces a distinct header value on every next() call, even when users reach the feed action concurrently. hasNext() returns true because the generator has no fixed stock. The data is generated only when a virtual user requests a record, so you do not prebuild a large list of IDs.
package example;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
public class FeederTutorialSimulation extends Simulation {
public FeederTutorialSimulation() {
var searchTerms = Boolean.getBoolean("reuseCsv")
? csv("data/searches.csv").circular()
: csv("data/searches.csv").queue();
var items = jsonFile("data/items.json").circular();
AtomicLong sequence = new AtomicLong();
Iterator<Map<String, Object>> requestIds = new Iterator<>() {
@Override
public boolean hasNext() {
return true;
}
@Override
public Map<String, Object> next() {
return Map.of("requestId", "tutorial-" + sequence.incrementAndGet());
}
};
ScenarioBuilder catalog = scenario("Catalog feeder tour")
.feed(searchTerms)
.feed(items)
.feed(requestIds)
.exec(http("search #{term}")
.get("/search")
.queryParam("term", "#{term}")
.header("X-Request-Id", "#{requestId}")
.check(status().is(200)))
.exec(http("item #{itemId}")
.get("/items/#{itemId}")
.queryParam("category", "#{category}")
.check(status().is(200)));
setUp(catalog.injectOpen(atOnceUsers(Integer.getInteger("users", 3))))
.protocols(http.baseUrl("http://127.0.0.1:8089"));
}
}
Each .feed(...) call consumes one record before either request runs. The feed actions write five Session attributes: term, itemId, category, and requestId from the three sources. The search uses its term and generated header; the item request uses the JSON fields. The sample leaves the values in the Session for both HTTP actions. If a later feed uses an existing key, it replaces that attribute, so give unrelated datasets distinct column names.
Verify Step 5: Keep the stub running and execute ./mvnw gatling:test -Dgatling.simulationClass=example.FeederTutorialSimulation. The console should show three users, six requests, and zero failed requests. Open the generated report under target/gatling/ and confirm that the search and item request names appear. A build error is a source or dependency problem; an HTTP 400 means the stub rejected a request value; a connection error means the local server is absent or the port differs. Resolve those separately.
Step 6: Prove Queue Exhaustion and Circular Reuse
The default CSV builder in the class uses queue(). Run a fourth user to make the finite stock fail intentionally. This is a test of data sizing, not an application performance test. The JSON feeder will not exhaust because it uses circular(); the custom iterator also continues to provide values.
./mvnw gatling:test -Dgatling.simulationClass=example.FeederTutorialSimulation -Dusers=4
Verify Step 6, part A: Expect the run to fail when the fourth user reaches the CSV feed action. Gatling reports that the feeder is empty. The exact mix of completed HTTP requests can depend on scheduling; do not assert that the first three users finish before the fourth tries to feed. The meaningful observation is the empty CSV queue, not a specific partial report count. If the run succeeds, check whether reuseCsv was set globally in your Maven environment or whether the source file still calls circular() unconditionally.
Now reuse the three search terms in order and run four users. The Boolean property selects the other branch of the same complete Java class, so no code edit is needed.
./mvnw gatling:test -Dgatling.simulationClass=example.FeederTutorialSimulation -Dusers=4 -DreuseCsv=true
Verify Step 6, part B: Expect four users, eight HTTP requests, and zero failed requests. CSV rows are consumed in their file order and the fourth feed wraps back to shoes. The JSON file cycles after its second row, so the four item IDs are 101, 202, 101, and 202. Request execution order in the console may interleave under concurrent users, even though each circular feeder advances through its own order. If this workflow requires unique credentials, do not switch it to circular merely to silence exhaustion; supply at least one credential record per planned feed call.
Step 7: Inspect the Requests Before Scaling the Test
The stub proves that required fields are present, but 200 alone cannot prove that each user received the intended row. For a stronger local diagnostic, run the simulation once with one user, then inspect the request details in the Gatling report or enable temporary HTTP request logging in a private development environment. Do not leave payload logging enabled for a production-like run: it can expose credentials and add generator overhead. Keep the first inspection run tiny, then restore normal logging.
./mvnw gatling:test -Dgatling.simulationClass=example.FeederTutorialSimulation -Dusers=1
Verify Step 7: The run should have one search and one item request with no failures. Confirm the report belongs to this newest run, since target/gatling/ can contain previous results. The request labels search shoes and item 101 show which values reached the HTTP action. The X-Request-Id header is checked by the stub but intentionally not exposed in the report label. If you need proof of uniqueness, add safe diagnostic logging or a purpose-built echo endpoint in your test environment, then remove that instrumentation before a larger run.
Gatling Feeders Tutorial: Choose the Right Strategy
A strategy is a data contract. Choose it from the business semantics of the endpoint, not from which option makes a failing load test turn green. The same CSV parser can safely reuse a search word but must not recycle a one-time registration email. JSON records follow the same principle. A custom iterator can make values indefinitely, but its generator still needs concurrency safety and a realistic value distribution.
| Strategy | Reuses records? | Order | Typical use | Failure to watch |
|---|---|---|---|---|
queue() |
No | Source order | Unique accounts, order IDs | Stock runs out |
shuffle() |
No | Randomized finite stock | Unique records with varied order | Stock still runs out |
random() |
Yes | Random selection | Read-only search terms | Duplicate values may distort cache hits |
circular() |
Yes | Source order, then wrap | Repeatable read-only catalog IDs | Reuse may violate uniqueness |
| Custom iterator | Defined by your code | Defined by your code | Generated IDs or remote data | Race conditions and unbounded generation |
One user can consume more than one row. If a scenario loops three times around .feed(csvFeeder), ten injected users may need thirty unique records under queue(). Conversely, a feed placed before the loop supplies one row per user that is reused within that user's iterations. Draw the scenario path and count feed executions before deciding how many rows to prepare.
Gatling Feeders Tutorial: Validate Data Coverage
A successful HTTP status is necessary but insufficient. Ask what values were actually exercised. The three-row CSV and two-row JSON file produce six possible term-item combinations, but this scenario makes only three pairings in the default run. It does not cover every combination. The two feeders advance independently and attach their next values to the same Session; they do not perform a Cartesian product. If you need every pairing, construct an explicit combined dataset or nest controlled iteration around one dimension.
Keep data validity checks outside the timed request path when practical. Check CSV headers, JSON root shape, key names, data types, and expected row counts before injecting load. Then let in-scenario Gatling checks validate the application's response. This separation makes a 400 from bad test data easier to distinguish from a server regression. It also prevents an empty queue from appearing halfway through a paid or time-sensitive run.
Track the ratio between unique and repeated values. A circular catalog feeder may be correct for a read-only endpoint yet create unrealistic cache warmth if a production population contains millions of items. A small random file can have the same issue. Expand or weight the source based on observed traffic, document the selection model, and avoid claiming representative results from this tutorial's tiny dataset. For API-focused workload design, compare API performance testing techniques and Gatling GraphQL load testing.
Troubleshooting
- Problem: Gatling cannot find
data/searches.csv. -> Put the file atsrc/test/resources/data/searches.csv, then callcsv("data/searches.csv"). Do not includesrc/test/resourcesin the classpath reference. Check case sensitivity on CI hosts. - Problem: A queue is empty during a larger run. -> Count every feed execution, including loops and retries, and provide at least that many unique rows. Select
circular()only when repeated values are semantically safe. - Problem:
#{term}or#{itemId}remains unresolved. -> Match the CSV header or JSON object key exactly and place.feed(...)before the HTTP action. Inspect the source file for a byte-order mark or unexpected field name. - Problem: The JSON feeder fails to parse. -> Run
python3 -m json.tooland confirm the top-level value is an array of objects. Do not givejsonFilea single bare object. - Problem: Requests fail to connect. -> Keep
java MockCatalog.javarunning in another terminal and verify port 8089. A connection failure happens before an HTTP status check can tell you anything about feeder values. - Problem: Concurrent custom IDs repeat. -> Make shared generator state thread-safe, as the example does with
AtomicLong. A plain mutable counter shared by virtual users can lose increments. Confirm uniqueness at the target if that property matters to the test.
Interview Questions and Answers
Q: When does Gatling read the next feeder record?
At the .feed(...) action, when a virtual user reaches that point in the scenario. A feed at scenario start consumes once per user; a feed inside a loop consumes on each iteration. The resulting map becomes Session attributes for subsequent actions.
Q: Why did the fourth user fail with only three CSV rows?
The default queue() strategy gives each feed call a distinct row and has no record left for call four. I would calculate total feed calls from the scenario path, then add enough unique records or choose a reuse strategy if the data can safely repeat.
Q: How does a JSON feeder differ from a CSV feeder?
jsonFile reads an array of objects and preserves values such as numbers, while CSV columns are convenient for flat tabular data and typically start as strings. Both expose keys through the Session. I choose based on source shape, data types, and maintenance needs.
Q: Is circular() safe for user credentials?
Usually not when concurrent sessions or one-time operations require distinct accounts. It repeats records after the source ends. I would reserve unique credential rows with queue() and size that file for the maximum number of feed executions.
Q: What makes a custom feeder valid in the Java DSL?
It supplies an Iterator<Map<String, Object>>, with each map carrying the keys that later actions read. The iterator must behave correctly under the concurrency of the run. I would make mutable shared state thread-safe and measure whether data generation itself constrains load.
Q: How would you verify feeder data reached the server?
First, give the target an explicit validation rule for required fields and check its status. For stronger evidence, inspect a small run's request details or correlate a safe request ID with server logs. A passing status without input validation can conceal a feeder mistake.
Common Mistakes
Do not treat queue exhaustion as a performance failure. It is a test-data capacity failure and should be corrected before interpreting latency charts. Do not use tiny circular datasets for benchmarks that claim realistic cache behavior. Keep sensitive feeder files out of public repositories and report artifacts; the tutorial values are intentionally synthetic. Avoid expensive API calls inside a custom next() implementation unless feeder latency is itself part of the experiment.
If you change the order of .feed(...) actions, check for duplicate keys. Gatling adds feeder records to the Session, and a later value with the same key can overwrite an earlier one. Use names such as searchTerm and itemId when combining datasets with overlapping fields. Finally, keep request checks specific: status().is(200) is enough for the local plumbing exercise, but a real API test should assert the returned item or search result matches the supplied input.
Where To Go Next
Replace the stub with a permitted staging endpoint and start with one user. Add realistic response checks, a dataset sized to the expected feed count, and a load profile that reflects arrivals rather than this tutorial's atOnceUsers diagnostic. Read Gatling basics for testers for the wider execution model and Gatling scenario design for pacing and journey design. Use senior Gatling interview questions to practice explaining why you chose each feeder strategy.
The decisive next exercise is a read and a write. Keep a circular feeder for a read-only catalog lookup, then create a separate queue of unique synthetic IDs for a write endpoint. Verify the read can repeat without changing state and the write refuses duplicate identifiers. That contrast turns the feeder API into a defensible workload design decision.
Conclusion
CSV, JSON, and custom Gatling feeders all deliver maps into a virtual user's Session, but they solve different data problems. CSV is a convenient flat source, JSON carries structured records, and a custom iterator generates values on demand. The critical choice is whether values must be unique, can repeat, or need to be produced dynamically.
Run the three-user example, trigger the intentional queue exhaustion, and inspect the four-user circular run. Once those behaviors are clear, adapt the same pattern to a real service with meaningful response checks and a dataset that matches your workload.
Interview Questions and Answers
At what point does a Gatling virtual user receive a feeder record?
A user gets a record when it reaches the feed action, not when the simulation class is constructed. Gatling inserts the record's map entries into that user's Session. A feed inside a loop consumes on each pass, which changes the dataset size needed for queue.
How would you select queue, shuffle, random, or circular for test data?
I start with whether values may repeat. Queue and shuffle are finite and unique; shuffle changes the order. Random and circular reuse values, with random selection or ordered wraparound respectively. I then check whether that selection changes cache behavior or violates an endpoint's uniqueness constraint.
Why might a test with ten users need thirty CSV records?
If every user reaches the CSV feed three times, the queue receives thirty requests for records. Injection count alone does not determine data demand. I trace loops, retries, and branches to calculate the maximum number of feed executions.
How do you implement a custom feeder safely in Gatling's Java DSL?
I supply an Iterator<Map<String, Object>> and emit maps with stable key names expected by downstream actions. When it shares mutable state across users, I use a thread-safe primitive or another concurrency-safe source. I also benchmark the feeder path so data generation does not throttle the intended arrival rate.
What does a feeder-empty error tell you about the system under test?
Nothing reliable about the target's latency or capacity. It indicates that the test script requested more unique records than the feeder contained. I correct dataset sizing or the chosen strategy, rerun a small validation, and only then interpret application results.
How would you verify that a JSON feeder is configured correctly?
I parse the file before the run and confirm that its root is an array of objects. I check key spelling and scalar types against the expressions in the request. Then I run one virtual user against an endpoint that validates the interpolated fields.
Why is circular reuse risky for performance analysis?
Repeated values may create unusually warm caches or trigger duplicate-write behavior. That can distort both success rates and response-time distributions. I document the intended data population and use enough distinct values to reflect production traffic where representativeness matters.
What is the difference between a feeder and a Gatling Session?
A feeder is a source of maps; the Session is a virtual user's evolving state. A feed action takes the next map from the source and writes those keys into that user's Session. Subsequent actions read them through expressions, and later actions can overwrite the same keys.
Frequently Asked Questions
Where should Gatling CSV feeder files go in a Maven project?
Put them under src/test/resources, such as src/test/resources/data/searches.csv. Reference that file with csv("data/searches.csv") so Gatling loads it from the test classpath. Including src/test/resources in the DSL path is a common cause of file-not-found errors.
What is the required shape of a Gatling JSON feeder file?
The top-level JSON value must be an array of objects. Each object becomes one feeder record whose keys can be used as Session attributes. Validate the file with a JSON parser before starting a load test.
What happens when a Gatling queue feeder runs out?
A queue consumes each record once and fails when another feed call asks for a missing record. Count every execution of the feed action, including loops, to size the data file. Reuse strategies are suitable only when repeating values matches the business operation.
What is the difference between queue and circular feeders?
Queue preserves source order and consumes each row only once. Circular preserves order but wraps to the first row after the last, so users can receive repeated values. Unique accounts usually need queue; read-only catalog IDs can often use circular.
Can a custom Gatling feeder generate unlimited IDs?
Yes. In the Java DSL, provide an Iterator<Map<String, Object>> whose hasNext returns true and whose next method supplies a map. Use thread-safe state such as AtomicLong when concurrent users share the iterator, and ensure the generated distribution is realistic for the test.
How do I know feeder values reached my HTTP request?
Put feed actions before the HTTP action, interpolate their keys with Gatling expressions, and make a small test target validate the resulting path, query, or header. Inspect a one-user run and correlate safe request identifiers with server logs when you need stronger proof.
Can I combine CSV and JSON feeders in one Gatling scenario?
Yes. Add separate feed actions before the requests that use their attributes. Give the files distinct keys to avoid overwriting Session attributes, and remember that the two feeders advance independently rather than producing every possible combination.
Related Guides
- pytest Markers Tutorial: skip, xfail and Custom Markers
- Appium 3 Config File Setup Tutorial: capabilities and .conf (2026)
- Gatling Java DSL Tutorial for Testers
- k6 setup and teardown Tutorial: Share an Auth Token Across VUs
- Playwright drag and drop: Examples and Best Practices
- Playwright mock date and time: Examples and Best Practices