skip to content

A Gatling simulation declares csv("src/main/resources/credentials.csv") and dies before a single virtual user starts, although the file is exactly there. Why?

level: middleimportance: must knowfreq 55%

answer

  1. the path is not a project path
  2. classpath root, so drop the prefix
  3. it fails in the constructor, not mid-run
  4. FileNotFoundException: Could not locate feeder file
  5. three refused prefixes, src/main/resources among them

basics

~10 s

Gatling resolves a feeder path against the classpath, not the project tree, and specifically refuses paths starting src/main/resources/, src/test/resources/ or src/gatling/resources/. Use credentials.csv. The check runs while the Simulation constructor evaluates csv().

solid answer

~40 s

The string handed to `csv` is a classpath path. `src/main/resources` is a classpath *root*, so once the project is built the file's classpath name is just `credentials.csv` — the prefix no longer exists. Gatling does not simply fail to find it: it carries an explicit list of three wrong prefixes, `src/main/resources/`, `src/test/resources/` and `src/gatling/resources/`, and refuses any path beginning with one, with a message telling you to drop it and showing the corrected form. The failure surfaces as a `FileNotFoundException` reading *Could not locate feeder file*, followed by a hint naming the line of your simulation that declared it. It is thrown while `csv(...)` is evaluated, which in a Simulation is the constructor, so the run dies before `setUp` completes and before any user is injected.

code

java · 22 lines
java
import io.gatling.javaapi.core.*;

import static io.gatling.javaapi.core.CoreDsl.*;

public class LoginSimulation extends Simulation {

  // WRONG: a project path. Refused before the run starts.
  // csv("src/main/resources/data/credentials.csv");

  // Also wrong: absolute, but pointing inside a source tree.
  // csv("/home/me/proj/src/main/resources/data/credentials.csv");

  // RIGHT: relative to the classpath root. Resolved right here,
  // in the constructor, long before any user is injected.
  FeederBuilder.FileBased<String> credentials = csv("data/credentials.csv");

  ScenarioBuilder login = scenario("login").feed(credentials);

  {
    setUp(login.injectOpen(atOnceUsers(1)));
  }
}

go deeper

for a junior

Recognise the symptom and the fix: drop the src/main/resources prefix and pass the path relative to that folder. Knowing that much resolves the error the first time you meet it.

for a middle

Explain the resolution order — classpath first, filesystem path second — and why Gatling refuses a path naming your source tree on purpose rather than merely failing to find it.

for a senior

Read the stack trace to the declaring line, and say why failing in the constructor is the good outcome and which feeder problems instead surface partway into a run.

for a principal

Decide where feeder data lives across the estate: packaged in the artifact for reproducibility, or on the generator's disk when it is too large or too sensitive to commit, and enforce that choice in review.

## The two roots are not the same root `src/main/resources` is a **classpath root**. Everything under it is copied into the build output and addressed from the root of the classpath, so a file living at `src/test/resources/data/credentials.csv` in your editor is simply `data/credentials.csv` once the project is built. The `src/test/resources/` part is a fact about your source tree; it does not exist at run time. Passing it to `csv` is asking Gatling for a classpath entry that, by construction, is never there. ## Gatling does not merely fail to find it — it refuses it Resolution is a two-stage match. Gatling first tries the string as a classpath resource, and only if that yields nothing does it try it as a filesystem path naming an existing regular file — resolved against the JVM's working directory when the path is relative. Anything else is a failure. The docs recommend the absolute form for that second stage, but the resolver never asks whether the path is absolute: the test is `Files.isRegularFile`, and nothing more. Layered on top of that is an explicit blocklist of three prefixes: * `src/main/resources/` * `src/test/resources/` * `src/gatling/resources/` A path **starting** with one of them is rejected at the classpath stage with a dedicated message telling you the path should be relative to the classpath root and giving the corrected form. A path that reaches the filesystem stage and **contains** one of them anywhere — `/home/ci/project/src/main/resources/credentials.csv`, and equally the relative `./src/main/resources/credentials.csv` — gets its own message saying you should not point at a directory that belongs to your classpath. That second case is the striking one: the file genuinely exists at that path and Gatling still refuses it, on purpose, because accepting it would make the simulation work on your machine and fail from the built artifact. ## What the failure looks like, and when it happens The feeder builders wrap the resolution failure and throw a `FileNotFoundException` whose message begins **`Could not locate feeder file:`**, followed by the specific reason and then a hint naming the frame in your own code that declared the feeder — rendered as `(defined at: ...)`. That hint is deliberately computed by walking the stack to the first non-Gatling frame. The timing matters as much as the message. `csv(...)` resolves its path **as the call is evaluated**. In a Simulation that call sits in the constructor, and Gatling instantiates a simulation by calling its no-argument constructor before anything else happens. So the run dies: 1. before `setUp` has finished registering the populations, 2. before the injection profile schedules anything, 3. before one virtual user exists and before one request is sent. You get a stack trace and no report, not a run that limps along and fails a few users. That is what the leaf's *fails first* means, and it is the pleasant version of this bug: the alternative would be discovering it twenty minutes into a soak. ## The resolution table | what you pass | what Gatling does | |---|---| | `data/credentials.csv` | resolves from the classpath root — the intended form | | `src/main/resources/data/credentials.csv` | refused with the drop-the-prefix message | | `/opt/gatling-data/credentials.csv` | accepted, if it names an existing regular file | | `./local-data/credentials.csv` on the generator's disk | accepted too — the fallback stage checks `isRegularFile`, not `isAbsolute` | | `/home/me/proj/src/main/resources/credentials.csv` | refused, even though the file is there | | `credentials.csv` when the file is one directory down | refused — no recursive search | ## Two related failures that are not this one Not every feeder problem surfaces in the constructor, and knowing which is which saves diagnosis time: * An **empty** CSV — a header line and nothing else — resolves perfectly well. It only fails when a virtual user tries to draw a record. * The eager feeders behave differently again. A `sitemap` file is parsed the moment you declare it and reports a missing file as an `IllegalArgumentException` reading `Could not locate sitemap file:` rather than a `FileNotFoundException`; and any source that ends up with zero records — an empty sitemap, a `jdbcFeeder` query that matched nothing, an empty array — fails at declaration with `requirement failed: Feeder must not be empty`. ## The fix Change the path, not the file layout. Move `credentials.csv` to `src/test/resources/data/credentials.csv`, declare `csv("data/credentials.csv")`, and the same simulation works from the IDE, from Maven or Gradle, and from a packaged jar. If the data genuinely must live outside the artifact — a 200,000-row credentials file you would rather not commit, for instance — use a full absolute path to a directory that is *not* part of any source tree, and make sure every load generator has it.

  • Can a Gatling feeder read a file that is not on the classpath at all?
    Yes. A filesystem path is accepted, which is how you deploy data files separately from the packaged simulation. The docs recommend an absolute one, but the resolver only asks that the path name an existing regular file, so a path resolved against the run's working directory is taken as well. Gatling tries the classpath first and falls back to that check second. A filesystem path that still contains a resources source directory is refused with its own message.
  • The simulation runs from a packaged jar. Does a classpath feeder path still work?
    Yes. When the resource resolves to a `jar:` URL, Gatling copies it out to a temporary file on first access and reads from there, so a feeder packaged inside the artifact behaves the same as one on an exploded classpath. That copy is why the path form, not the packaging, is what you have to get right.
  • Does an empty CSV file fail at declaration in the same way?
    No. A header-only CSV resolves fine and fails only when a virtual user tries to draw a record. The eager sources differ: a sitemap file, a `jdbcFeeder` query or an in-memory array that yields no records fails at declaration with *requirement failed: Feeder must not be empty*.

It is the difference between a book's shelf mark and the address of the warehouse it was packed in. Once the book is on the shelf, the warehouse address describes nothing you can look up.

saying these in an interview costs you the question

  • Believing a src/main/resources/ path works because the file is there on disk.
  • Expecting the failure at the first feed step rather than at declaration.
  • Assuming a missing feeder file only fails the virtual users that reach it.
  • Thinking a filesystem path pointing inside src/main/resources is accepted.