Parameterization & Test Data

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page was written against Apache JMeter 5.6.3 (the latest release on 2026-10-14; it runs on Java 8 and later, and Java 17 or later is recommended), with Java 17 or later (21 recommended) as the runtime used by the examples. JMeter 6.0 is not released: the changes planned for it are listed in xdocs/changes.xml on the master branch, need Java 17 or later (21 recommended), and none of them touches the test-data elements on this page, so everything below is expected to carry over; the planned move to Groovy 5 only matters for the JSR223 examples. The Groovy examples use the Groovy engine bundled with 5.6.3. Plugins Manager 2.0, jmeter-maven-plugin 3.8.x and the gRPC Request plugin 2.0.x are not needed here. The field names, defaults and behaviour come from the official component reference, functions reference, properties reference and change log; where the documentation is silent and the 5.6.3 source code was used instead, the text says so. The books on JMeter (2.11 to 3.0) are used for the methodology of test data only.

A load test that sends the same request again and again measures a system that does not exist in production. The Bookshelf plan built in this section therefore takes its users from users.csv and its search terms from search-terms.csv, and adds generated values (counters, random ids) where a file would be overkill. This page explains why that matters, every option JMeter offers for feeding data into a plan, how much each one costs on the load generator, and how to keep the data sound between runs and across several load generators.

Why hard-coded data skews results

Recording a journey gives a plan with literal values: one username, one password, one search term, one book id. Run that with 50 threads and the server sees 50 copies of one user doing one thing. The measured response times are then wrong in predictable directions:

Effect What happens with one repeated value Direction of the error

Caching

The first GET /api/books?query=tolkien misses every cache layer (database buffer pool, ORM second-level cache, application cache, CDN or reverse proxy). Every later call hits them. Production traffic spreads over thousands of terms and many miss.

Too optimistic

Locks and contention

50 threads that create a loan on the same book, or update the same user row, queue on the same row lock or optimistic version. Production users rarely collide like that.

Too pessimistic

Uniqueness and business rules

A second POST /api/loans for a book that is already on loan, or a second registration with the same e-mail, returns a 409 or 422 error. The error is fast, so the averages improve while the test is in fact broken.

Wrong, often hidden

Sessions and server state

One account logged in 50 times may hit a per-user session limit, a rate limit or a token-revocation rule, or share a per-user cache entry.

Either direction

Data distribution

Real users search popular and rare terms, open short and long books, and own few or many loans. One value exercises one execution path and one index range.

Unrepresentative

The Pro Apache JMeter book (Pro ch. 11) makes test data part of the workload model, next to the use-case mix and the think times: the volume and variety of the data in the database and in the requests must resemble production for the results to transfer. The JMeter manual gives the same advice from the other side. Its best-practices chapter suggests using one sampler in a loop fed by variables from a CSV Data Set, rather than many similar samplers, and preparing large or randomised data in a file before the test instead of generating it at run time.

The Bookshelf data files

The Bookshelf plan keeps its data next to the test plan, under version control, in a data/ folder:

bookshelf-perf/
├── bookshelf-load.jmx
└── data/
    ├── users.csv           one test account per line (placeholder credentials)
    └── search-terms.csv    one search term per line, popular terms repeated

users.csv has a header line and one test account per line. The accounts must exist in the test environment (they are seeded through the API or the database before the run, as described under Test-data hygiene). The values are placeholders that only exist in a disposable test database; never put real credentials in a data file.

username,password
reader0001,change-me-0001
reader0002,change-me-0002
reader0003,change-me-0003
...
reader0500,change-me-0500

search-terms.csv has a single column. Popular terms appear several times so that the mix of cache hits and misses is closer to the real one, and some values contain a comma, which is why they are quoted:

term
tolkien
tolkien
distributed systems
"le guin, ursula"
"pratchett, terry"
kotlin
kotlin
kotlin
history of rome
"""clean code"""

The last line is the quoted form of the text "clean code", including its double quotes, as explained under the Allow quoted data? field below.

CSV Data Set Config

What it is and where it sits

The CSV Data Set Config is a configuration element that reads one line of a file and splits it into variables. The component reference describes it as easier to use than the __CSVRead() and __StringFromFile() functions and well suited to large numbers of variables and to "random" and unique values. It is the default answer to "where does this value come from?" in a JMeter plan.

Add it with Add → Config Element → CSV Data Set Config, normally as a direct child of the Thread Group (or of the Test Plan when several Thread Groups share it). Three facts from the component reference drive everything else:

  • Lines are read at the start of each test iteration. The variables are therefore stable for the whole iteration: every sampler of the Bookshelf journey sees the same ${username}.

  • The file name and the sharing mode are resolved in the first iteration, so they can contain variables and functions such as ${__threadNum} or ${__P(node,0)}, but changing them later has no effect.

  • Variables are defined after configuration processing. Elements that read their settings once at configuration time, such as the JDBC Connection Configuration, cannot use CSV variables. Elements that read their values at run time, such as the HTTP Authorization Manager, can.

Which "iteration" counts depends on where the element is placed. In the 5.6.3 source (TestCompiler), an element that listens to iterations, such as the CSV Data Set Config, the Counter or the Random Variable, is attached to the nearest enclosing controller. Directly under a Thread Group it advances once per Thread Group iteration; under a Loop Controller it advances once per pass of that loop. The Bookshelf search loop relies on this: a CSV Data Set Config for search-terms.csv placed inside the three-pass Loop Controller gives three different terms per visit (see Logic Controllers & Transactions).

As a configuration element it is processed before pre-processors, timers and the sampler of its scope (execution order and scoping are on Test Plan Elements, Execution Order & Scoping). The variables it sets are ordinary thread variables (see Properties, Variables & Functions), so an extractor or a script can overwrite them later in the same iteration.

Every field

Field (GUI) Meaning JMX property Default Required

Name

Descriptive name shown in the tree. Naming it after the file (users.csv) makes plans easier to read.

testname attribute

CSV Data Set Config

No

Filename

File to read. A relative name is resolved against the directory of the active test plan (the .jmx). Absolute names work, but are unlikely to work in remote mode unless every server has the same directory structure. The same physical file named in two ways (users.csv and ./users.csv) counts as two different files, each with its own read position; on a case-insensitive file system Users.CSV is a third one. Since 3.1, leading and trailing spaces are trimmed.

filename

empty

Yes

File Encoding

Character set used to read the file. Empty means the platform default of the JVM. The drop-down offers the values of the csvdataset.file.encoding_list property (UTF-8, UTF-16, ISO-8859-15 and US-ASCII in the shipped jmeter.properties); any other Java charset name can be typed.

fileEncoding

empty

No

Variable Names

Names of the variables, in column order. Leave it empty to read the first line of the file as the list of names (a header line). The component reference says the names are separated by the delimiter and may be quoted; the GUI label reads Variable Names (comma-delimited), and the 5.6.3 source splits a typed list on commas, so use commas here even when the file uses another delimiter. Since 3.1 names are trimmed, so username, password works.

variableNames

empty

No

Ignore first line (only used if Variable Names is not empty)

Skip the first line of the file. Use it when the file has a header line but the names are typed in Variable Names anyway, for example to rename the columns. With Variable Names empty the first line is always the header and this option does nothing.

ignoreFirstLine

false

No

Delimiter (use '\t' for tab)

Character that separates the values. The two characters \t mean a tab. In 5.6.3 an empty delimiter falls back to a comma (source). If a line has fewer values than there are variables, the remaining variables are not updated and keep their previous value.

delimiter

,

Yes

Allow quoted data?

When enabled, a value may be enclosed in double quotes so that it can contain the delimiter, and the quotes are removed. A double quote inside a quoted value is written as two double quotes. Quoted values may also contain line breaks. When disabled, quotes are ordinary characters.

quotedData

false

Yes

Recycle on EOF ?

On reaching the end of the file, start again from the first data line.

recycle

true

Yes

Stop thread on EOF ?

On reaching the end of the file with Recycle off, stop the thread. Has no effect while Recycle is on.

stopThread

false

Yes

Sharing mode

Which threads share one read position in the file: All threads, Current thread group, Current thread, or any other text, which acts as an identifier. See Sharing modes.

shareMode

shareMode.all (All threads)

Yes

Two JMeter properties complete the element:

Property Meaning Default

csvdataset.eofstring

Value given to every variable at the end of the file when neither recycling nor stopping is enabled.

<EOF>

csvdataset.file.encoding_list

List of encodings offered in the File Encoding drop-down, separated by |.

UTF-8|UTF-16|ISO-8859-15|US-ASCII in the shipped jmeter.properties

File paths: relative to the plan

Relative file names are resolved against the folder of the test plan that is being run, not against the folder JMeter was started from. A plan saved as bookshelf-perf/bookshelf-load.jmx with Filename data/users.csv therefore finds bookshelf-perf/data/users.csv whether it is run from the GUI, from bookshelf-perf/ or from a CI job that calls jmeter -n -t bookshelf-perf/bookshelf-load.jmx. Keep data paths relative and inside the project, with forward slashes, so the same plan works on Windows, macOS, Linux and in containers (since 5.0 the GUI stores the file name as typed instead of converting the separators).

Absolute paths make the plan depend on one machine layout. If a path really has to change per environment, read it from a property instead: ${__P(usersFile,data/users.csv)} in Filename, and -JusersFile=/mnt/data/users.csv on the command line.

Variable names versus a header line

There are three ways to name the columns of users.csv:

Variable Names Ignore first line File Result

empty

(ignored)

header username,password, then data

Variables username and password come from the header. The header is never used as data. Recommended: the file is self-describing and adding a column does not require editing the plan.

username,password

false

data only, no header

Variables come from the field. Every line is data.

user,pass

true

header, then data

Variables user and pass; the header line is skipped. Useful when the plan expects other names than the file.

username,password

false

header, then data

A bug: the header is read as data, so one thread logs in as user username with password password.

With the header form, the delimiter must still be set correctly, because the header is split with it.

Delimiters and quoted data

The delimiter is a single character. For a tab-separated file, type the two characters \t. For a semicolon-separated export from a spreadsheet, type ;. Multi-character delimiters are not supported: when Allow quoted data? is on, the 5.6.3 source only uses the first character of the field.

With Allow quoted data? enabled, the component reference gives this example: the line 1,"2,3","4""5" is split into the three values 1, 2,3 and 4"5. In search-terms.csv, "le guin, ursula" becomes the single value le guin, ursula, and """clean code""" becomes "clean code" with its quotes. With the option disabled, the first of those lines would yield "le guin and ursula" (with a leading space), and only the first would be stored, since the file has one column.

Quoted parsing costs a little more CPU than a plain split, so leave it off for files that never contain the delimiter, such as users.csv.

End of file: recycle or stop

The two EOF options combine into three behaviours:

Recycle on EOF Stop thread on EOF Behaviour at the end of the file

true (default)

(ignored)

Reading starts again with the first data line. The file is a pool of values that is reused.

false

true

The thread that hits the end of the file stops. The 5.6.3 source throws JMeterStopThreadException with a message naming the file and the element; since 3.1 the log makes it clear that the thread exits because of this setting. When every thread has stopped, the test ends.

false

false

Every variable of the element is set to <EOF> (or the value of csvdataset.eofstring) and the thread carries on. The following requests then send <EOF> as a username, which usually shows up as a wave of 401 errors.

Which one to choose follows from what the data means:

  • Reusable values (search terms, book ids to view): recycle. A term may be searched many times.

  • Values that may be used only once (registration e-mails, one-time vouchers, books that each thread borrows and never returns): no recycle, stop the thread. The run then ends when the data runs out, which shortens the test silently, so size the file for the whole run (see Worked numbers: how many rows a run needs).

  • A fixed number of passes over the file (each line exactly once, for a data-load job): no recycle, stop the thread, sharing mode All threads, Thread Group loop count Infinite. JMeter stops when the file is exhausted.

  • No recycle, no stop is rarely what is wanted in a load test. Use it only with an If Controller that checks "${username}" != "<EOF>", for example to skip the rest of the iteration.

Sharing modes

By default the file is opened once and every thread takes the next line from a single shared read position. The Sharing mode field changes which threads share that position:

Sharing mode Who shares one read position Typical use

All threads (default)

Every thread of every Thread Group that uses the same file name (in all elements that use the default mode).

A pool of unique-ish values: each line goes to one thread at a time. The Bookshelf users.csv.

Current thread group

The threads of one Thread Group. Each Thread Group in which the element appears opens the file separately and starts at the first line.

Two Thread Groups of the mix (search-only and browse-and-loan) that should both walk through the whole list of terms.

Current thread

One thread. Each thread opens the file separately and reads it from the first line.

A per-user script: each line is the next step of the same user. With test${__threadNum}.csv as the file name, a separate file per thread.

Identifier (any other text)

All threads whose element uses the same identifier, in any Thread Group.

Sharing one pool between selected Thread Groups but not others; or ${__threadNum} as the identifier so that thread N of each group shares a position with thread N of the others.

The figure shows the rows read on the first iteration by four or five threads in two or three Thread Groups under each mode:

Rows of users.csv handed to threads under the All threads, Current thread group, Current thread and Identifier sharing modes

Two consequences of the default mode are easy to miss:

  • The order of lines across threads is not fixed. The component reference notes that lines go to threads in the order in which they run, which may differ between iterations. Thread 1 is not guaranteed to get line 1. If one user must always be the same thread, use Current thread with a file per thread, or __CSVRead (below).

  • Each thread reads a new line every iteration. With Same user on each iteration checked on the Thread Group the cookies are kept, but the CSV variables still change at the start of each iteration. A journey that logs in only once (in a Once Only Controller) keeps the session of the first account while ${username} shows later lines. Either log in on every iteration or read the account with a mode that does not advance.

The source shows how the modes work: All threads uses the file name itself as the key of the open file, Current thread group and Current thread append the identity of the Thread Group or of the thread, and an identifier is appended as typed. That is why users.csv and ./users.csv are two files, and why two elements that use the same file and the same identifier share one position even in different Thread Groups.

Step by step: adding users.csv to the Bookshelf plan

  1. Save the plan first, so that the relative path can be resolved: File → Save Test Plan as, bookshelf-load.jmx.

  2. Select the Thread Group Borrowers and choose Add → Config Element → CSV Data Set Config.

  3. Set Name to users.csv and Filename to data/users.csv. If the file is picked with Browse…​ and the field ends up with an absolute path, edit it back to a relative one.

  4. Set File Encoding to UTF-8.

  5. Leave Variable Names empty and Ignore first line unchecked: the header line provides username and password.

  6. Keep Delimiter ,, Allow quoted data? false, Recycle on EOF ? true, Stop thread on EOF ? false and Sharing mode All threads.

  7. In POST /api/auth/login, use the variables in the body:

    { "username": "${username}", "password": "${password}" }
  8. Add a Debug Sampler and a View Results Tree temporarily, run one thread for two iterations, and check that the two iterations log in as reader0001 and reader0002. Remove both before the load run.

The resulting fragment, together with the search terms read inside the search loop:

<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Borrowers">
  <!-- threads, ramp-up, duration and loop settings as in the load profile -->
</ThreadGroup>
<hashTree>
  <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="users.csv">
    <stringProp name="filename">data/users.csv</stringProp>
    <stringProp name="fileEncoding">UTF-8</stringProp>
    <stringProp name="variableNames"></stringProp>
    <boolProp name="ignoreFirstLine">false</boolProp>
    <stringProp name="delimiter">,</stringProp>
    <boolProp name="quotedData">false</boolProp>
    <boolProp name="recycle">true</boolProp>
    <boolProp name="stopThread">false</boolProp>
    <stringProp name="shareMode">shareMode.all</stringProp>
  </CSVDataSet>
  <hashTree/>
  <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /api/auth/login">
    <stringProp name="HTTPSampler.path">/api/auth/login</stringProp>
    <stringProp name="HTTPSampler.method">POST</stringProp>
    <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
    <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
      <collectionProp name="Arguments.arguments">
        <elementProp name="" elementType="HTTPArgument">
          <boolProp name="HTTPArgument.always_encode">false</boolProp>
          <stringProp name="Argument.value">{ "username": "${username}", "password": "${password}" }</stringProp>
          <stringProp name="Argument.metadata">=</stringProp>
        </elementProp>
      </collectionProp>
    </elementProp>
  </HTTPSamplerProxy>
  <hashTree/>
  <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="SearchLoop">
    <stringProp name="LoopController.loops">3</stringProp>
    <boolProp name="LoopController.continue_forever">true</boolProp>
  </LoopController>
  <hashTree>
    <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="search-terms.csv">
      <stringProp name="filename">data/search-terms.csv</stringProp>
      <stringProp name="fileEncoding">UTF-8</stringProp>
      <stringProp name="variableNames"></stringProp>
      <boolProp name="ignoreFirstLine">false</boolProp>
      <stringProp name="delimiter">,</stringProp>
      <boolProp name="quotedData">true</boolProp>
      <boolProp name="recycle">true</boolProp>
      <boolProp name="stopThread">false</boolProp>
      <stringProp name="shareMode">shareMode.all</stringProp>
    </CSVDataSet>
    <hashTree/>
    <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /api/books (search)">
      <stringProp name="HTTPSampler.path">/api/books</stringProp>
      <stringProp name="HTTPSampler.method">GET</stringProp>
      <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
        <collectionProp name="Arguments.arguments">
          <elementProp name="query" elementType="HTTPArgument">
            <boolProp name="HTTPArgument.always_encode">true</boolProp>
            <stringProp name="Argument.name">query</stringProp>
            <stringProp name="Argument.value">${term}</stringProp>
            <stringProp name="Argument.metadata">=</stringProp>
            <boolProp name="HTTPArgument.use_equals">true</boolProp>
          </elementProp>
        </collectionProp>
      </elementProp>
    </HTTPSamplerProxy>
    <hashTree/>
  </hashTree>
</hashTree>

The search term is sent as a parameter with URL Encode? checked (HTTPArgument.always_encode), because terms such as le guin, ursula contain spaces and commas; writing /api/books?query=${term} directly in the path, as the shorter fragments on other pages do, sends the term unencoded. The HTTP Request fields are covered on the HTTP Requests page of this section.

Worked numbers: how many rows a run needs

The Bookshelf load profile is 50 threads, a 60 s ramp-up and a scheduler duration of 660 s (the duration includes the ramp-up, which leaves a 10-minute hold). Assume one pass of the journey takes about 16 s: five requests of about 200 ms, each preceded by the section’s average think time of 3 s (Z = 3 s), so 5 × 3.2 = 16 s.

  • Thread k (k = 0 … 49) starts at 60 × k / 50 = 1.2 × k s and runs until 660 s.

  • Total thread time: 50 × 660 − 1.2 × (0 + 1 + … + 49) = 33,000 − 1,470 = 31,530 thread-seconds.

  • Iterations: 31,530 / 16 ≈ 1,970. With users.csv under the Thread Group, that is about 1,970 lines read.

  • Searches: the Loop Controller reads three terms per iteration, so about 5,910 lines of search-terms.csv.

What that means for the files:

  • With 500 accounts and Recycle on EOF on, each account logs in about 1,970 / 500 ≈ 3.9 times in the run, never twice at the same time as long as there are more lines than threads. That is fine for a login test.

  • If each iteration must use a fresh account (no recycle, stop the thread), the file needs at least 1,970 data lines, plus a margin for a faster-than-expected system: if the journey drops to 14 s, the run needs 31,530 / 14 ≈ 2,250 lines. Without the margin the threads stop one after another near the end and the last minutes of the hold run with fewer users than planned.

  • With 50 threads and only 40 lines in an All threads file, ten threads get a line that another thread is still using in the same moment. Keep at least as many lines as threads whenever concurrent use of one value matters (one account logged in twice, one book borrowed twice).

Cost on the load generator

The CSV Data Set Config is the cheapest way to vary data. In 5.6.3 the file is read line by line through a shared reader, so memory use does not grow with the file size, and each iteration costs one line read and one split. With the default mode, all threads take lines from one reader, so reads are serialised; at the rates a single load generator reaches this is negligible next to the HTTP work. Current thread mode opens one reader per thread, which costs one file handle and one buffer per thread: with 2,000 threads that is 2,000 open files, which can hit the operating system’s open-file limit.

The expensive alternative is generating values at run time. The component reference spells it out: generating unique random values at run time is expensive in CPU and memory, so create the data in advance, and if different values are needed in each run, combine the file data with a run-time parameter (for example by concatenation), which is much cheaper than generating everything at run time.

The Open Model Thread Group and CSV files

Under the Open Model Thread Group every arrival is a new thread that runs the children once, so a CSV Data Set Config with the default sharing mode gives each arrival the next line of the file (see Open Model & Throughput Control). JMeter 5.6 fixed a bug in which this combination skipped rows (PR#717 in the 5.6 change log). With 5.5, an open model run could therefore use fewer distinct lines than expected. Current thread mode makes little sense with the open model: each arrival is a new thread and would always read the first line.

Source: Changes history (5.6, PR#717)

Functions that read files: CSVRead and StringFromFile

Two older functions read data files without a configuration element. Both are catalogued on Properties, Variables & Functions; here is how they compare with the CSV Data Set Config.

Aspect __CSVRead(file,column) __StringFromFile(file,var,start,end)

What it returns

One column of the current line. Column 0 is the first; the special column next moves this thread to the next line and returns an empty string; *ALIAS opens a file under a shorter alias.

The whole next line of a text file (no splitting).

Read position

Each thread has its own pointer. On first use, a thread gets the next free row, so threads start on different rows; the row only changes when the thread calls next.

Shared by all threads. Every call reads the next line. Each reference to the function in the plan opens the file independently, even with the same name.

End of file

Wraps around. A blank line counts as the end of the file, which allows trailing comments.

Wraps around, unless the maximum loop count (end sequence number) is reached.

Memory

The whole file is loaded into memory on first use. Not for large files.

Line by line.

Path

Case-sensitive name, even on file systems that are not.

May be relative to the JMeter launch directory. With start and end numbers, the name is a DecimalFormat pattern, for example data/terms-#.txt, to read a series of files.

Delimiter

A comma, or the csvread.delimiter property.

Not applicable.

Errors

Logged.

Returns **ERR**.

The useful case for __CSVRead is the one the CSV Data Set Config handles awkwardly: one account per thread for the whole test. Because the row pointer only moves on next, a thread that reads ${__CSVRead(data/accounts.csv,0)} keeps the same row in every iteration. Put the calls in a User Parameters pre-processor so that both columns come from the same row; the functions reference shows exactly this pattern, adding that the lines are consecutive only when all calls are in the same User Parameters element:

User Parameters (Update Once Per Iteration: checked)
  Name       User_1
  username   ${__CSVRead(data/accounts.csv,0)}
  password   ${__CSVRead(data/accounts.csv,1)}

accounts.csv here has no header line, because the function reads every line as data. The file must have at least as many lines as threads, or two threads share one account.

__StringFromFile is mostly found in old plans. The component reference points out that the CSV Data Set Config is easier to use; the functions reference notes the one thing it can do that the element cannot, reading a numbered series of files.

User Defined Variables

User Defined Variables (Add → Config Element → User Defined Variables, or the table on the Test Plan itself) define the initial set of variables. The component reference is precise about when they are processed:

  • All User Defined Variables elements in the plan, wherever they are, are processed at the start of the test, from top to bottom, before any thread runs. The result is copied into every thread as its starting variables.

  • A function inside a value is evaluated once. ${__Random(1,500)} in a User Defined Variable gives every thread and every iteration the same number. The manual says they should not be used with functions that return different results on each call.

  • Functions that read properties are the intended use: HOST = ${__P(host,localhost)}.

  • They are shared between Thread Groups, so use distinct names for distinct values, and a variable cannot reference another one defined in the same element (earlier elements and the Test Plan are fine).

  • A run-time element (a User Parameters pre-processor, an extractor) that sets a variable with the same name replaces the value for that thread only.

Field Meaning

Name

Descriptive name shown in the tree.

User Defined Variables (table)

Name, Value and Description columns. The name is what goes inside ${...}. The buttons add, delete and move rows, and add rows from the clipboard.

For the Bookshelf plan they turn run properties into short variable names at the top of the plan:

<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="Run settings">
  <collectionProp name="Arguments.arguments">
    <elementProp name="HOST" elementType="Argument">
      <stringProp name="Argument.name">HOST</stringProp>
      <stringProp name="Argument.value">${__P(host,localhost)}</stringProp>
      <stringProp name="Argument.metadata">=</stringProp>
    </elementProp>
    <elementProp name="PORT" elementType="Argument">
      <stringProp name="Argument.name">PORT</stringProp>
      <stringProp name="Argument.value">${__P(port,8080)}</stringProp>
      <stringProp name="Argument.metadata">=</stringProp>
    </elementProp>
    <elementProp name="DATA_DIR" elementType="Argument">
      <stringProp name="Argument.name">DATA_DIR</stringProp>
      <stringProp name="Argument.value">${__P(dataDir,data)}</stringProp>
      <stringProp name="Argument.metadata">=</stringProp>
    </elementProp>
  </collectionProp>
</Arguments>

${DATA_DIR}/users.csv can then be used as the Filename of the CSV Data Set Config, because the file name is resolved in the first iteration, after the User Defined Variables have been processed.

User Parameters

User Parameters (Add → Pre Processors → User Parameters) give each thread its own value from a short series typed in the GUI. Each variable is a row; each user is a column. Thread 1 gets the first column, thread 2 the second, and when there are more threads than columns the values are reused (the 5.6.3 source picks column thread number modulo number of columns). Values may contain functions, which are evaluated each time the element runs. The component reference itself points to the CSV Data Set Config for large numbers of parameters.

Field Meaning JMX property

Name

Descriptive name shown in the tree.

testname attribute

Update Once Per Iteration

Checked: the variables are set once at the start of each iteration of the parent controller. Unchecked: they are set again before every sampler in the element’s scope, which matters when the values contain functions such as __counter or __CSVRead with next.

UserParameters.per_iteration

Table: Name column and one column per user

Variable names (UserParameters.names) and, for each user, the list of values (UserParameters.thread_values). Buttons: Add Variable, Delete Variable, Add User, Delete User, Up, Down.

UserParameters.names, UserParameters.thread_values

Bookshelf example: three locales, so that threads 1, 4, 7 … send en-GB, threads 2, 5, 8 … es-ES and threads 3, 6, 9 … fr-FR in an Accept-Language header:

<UserParameters guiclass="UserParametersGui" testclass="UserParameters" testname="Locale per user">
  <collectionProp name="UserParameters.names">
    <stringProp name="1">locale</stringProp>
  </collectionProp>
  <collectionProp name="UserParameters.thread_values">
    <collectionProp name="2">
      <stringProp name="3">en-GB</stringProp>
    </collectionProp>
    <collectionProp name="4">
      <stringProp name="5">es-ES</stringProp>
    </collectionProp>
    <collectionProp name="6">
      <stringProp name="7">fr-FR</stringProp>
    </collectionProp>
  </collectionProp>
  <boolProp name="UserParameters.per_iteration">true</boolProp>
</UserParameters>

The name attributes inside the collections are not used; the GUI writes hash codes there, any value loads.

Do not confuse it with RegEx User Parameters, a separate pre-processor that injects parameter names and values captured by a Regular Expression Extractor into an HTTP request; it belongs with correlation (see Correlation & Extractors).

Counter

The Counter (Add → Config Element → Counter) creates a number that grows by a fixed step at each iteration, and starts again from the beginning when it passes its maximum. It uses a long, so its range is −263 to 263 − 1. It is the right tool for values that must be unique within a run, such as an order reference or a numbered test e-mail.

Field Meaning JMX property Default

Name

Descriptive name shown in the tree.

testname attribute

Starting value

Value during the first iteration.

CounterConfig.start

0

Increment

Amount added after each iteration. 0 means no increment. Required.

CounterConfig.incr

0

Maximum value

When the counter would exceed it, it restarts at the starting value.

CounterConfig.end

Long.MAX_VALUE

Number format

Optional java.text.DecimalFormat pattern: 000 gives 001, 002 …; literal text is allowed, as in LN-000000. An invalid pattern is ignored.

CounterConfig.format

Long.toString()

Exported Variable Name

Variable that holds the value, read as ${loanRef}. Leaving it empty creates a variable with an empty name, which the manual strongly discourages.

CounterConfig.name

empty

Track counter independently for each user

Unchecked: one global counter, so on the first iteration user 1 gets the first value and user 2 the next. Checked: every thread has its own counter, which counts that thread’s iterations.

CounterConfig.per_user

false

Reset counter on each Thread Group Iteration

Only with a per-user counter: restart from the starting value at each Thread Group iteration. Useful when the Counter sits inside a Loop Controller and should count the passes of that loop.

CounterConfig.reset_on_tg_iteration

false

Bookshelf example: a unique, readable client reference for every loan created in the run, from LN-000001 up:

<CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Loan reference">
  <stringProp name="CounterConfig.start">1</stringProp>
  <stringProp name="CounterConfig.end"></stringProp>
  <stringProp name="CounterConfig.incr">1</stringProp>
  <stringProp name="CounterConfig.name">loanRef</stringProp>
  <stringProp name="CounterConfig.format">LN-000000</stringProp>
  <boolProp name="CounterConfig.per_user">false</boolProp>
</CounterConfig>

With the 1,970 iterations of the load profile, the references run from LN-000001 to about LN-001970. A global counter restarts at its starting value for each run, so if references must be unique across runs, combine it with a run identifier: ${__P(runId,local)}-${loanRef}, with -JrunId=$(date +%Y%m%d%H%M) on the command line. In distributed mode every server has its own counter, so add a per-server prefix too (see CSV files in distributed runs).

The __counter(TRUE|FALSE,var) function is the lightweight alternative: it starts at 1, increments by 1, uses an int (maximum 2,147,483,647), does not increment twice within one iteration, and each function instance keeps its own global counter. The Counter element offers a start value, a step, a maximum and a format, which the function does not.

Random Variable

The Random Variable (Add → Config Element → Random Variable) stores a random integer in a variable at the start of each iteration. The manual describes it as simpler than combining User Defined Variables with __Random(), which, as seen above, would not work anyway because a User Defined Variable is evaluated once. The number is computed as minimum + Random.nextInt(maximum - minimum + 1) and then formatted.

Field Meaning JMX property Required

Name

Descriptive name shown in the tree.

testname attribute

Yes

Variable Name

Variable in which the value is stored.

variableName

Yes

Format String

Optional java.text.DecimalFormat pattern, for example 000 (at least three digits) or USER_000 (USER_nnn). Empty means Long.toString().

outputFormat

No

Minimum Value

Smallest value, a long.

minimumValue

Yes

Maximum Value

Largest value, a long. The range (maximum − minimum) must be below 2,147,483,647. Since 3.2, minimum may equal maximum.

maximumValue

Yes

Random Seed

Seed of the generator. Empty uses the default java.util.Random constructor. The same seed with Per Thread true gives every thread the same sequence of values.

randomSeed

No

Per Thread(User)?

False: one generator shared by all threads (the element is not cloned per thread). True: one generator per thread.

perThread

Yes

Because the value is computed at the start of each iteration, the manual advises against using a variable (other than a property) for the minimum or the maximum: on the first iteration it would be zero. Use literals or ${__P(...)}.

Bookshelf example: view a random book among the 500 that the setUp Thread Group seeds, ids 1 to 500:

<RandomVariableConfig guiclass="TestBeanGUI" testclass="RandomVariableConfig" testname="Random book id">
  <stringProp name="variableName">bookId</stringProp>
  <stringProp name="outputFormat"></stringProp>
  <stringProp name="minimumValue">1</stringProp>
  <stringProp name="maximumValue">${__P(books,500)}</stringProp>
  <stringProp name="randomSeed"></stringProp>
  <boolProp name="perThread">false</boolProp>
</RandomVariableConfig>

GET /api/books/${bookId} then spreads the views uniformly over the catalogue. Uniform is often not realistic: real traffic concentrates on popular items, so a uniform spread under-uses the caches and overstates the cost of a view. To reproduce a skewed popularity, use a CSV file of ids in which popular books appear more often, like search-terms.csv, or extract the ids from the search response (correlation). A fixed Random Seed gives the same sequence in every run, which helps when comparing two builds of the system.

Choosing a data source

Source Values come from Changes Load-generator cost Best for

CSV Data Set Config

A file, line by line

Each iteration of the enclosing controller

Very low; memory independent of file size

Almost all test data: accounts, search terms, ids, payloads

__CSVRead

A file loaded into memory

When the thread calls next

Memory proportional to file size

A fixed row per thread for the whole test

__StringFromFile

A text file, one line per call

Every call

Low, but a lock per call (it is shared)

Old plans; a numbered series of files

User Defined Variables

Typed values and properties

Never (evaluated once)

None

Run settings: host, port, data folder

User Parameters

A small table, one column per user

Each iteration or each sample

Low

A handful of per-user values, or functions evaluated per sample

Counter / __counter

A generated sequence

Each iteration

Very low

Unique references within a run

Random Variable / __Random

A generated number

Each iteration / each call

Very low

Ids in a known numeric range

JSR223 element (Groovy)

Any code

Each execution

From low to high, depending on the script

Data derived from other variables, last-resort logic

Generating data before the run

The manual’s advice is to prepare large or randomised data before the test rather than during it. Two ways fit the Bookshelf project.

Offline, with a script under version control

A small Groovy script writes users.csv. It runs with any Groovy installation, outside JMeter, so it costs the load generator nothing during the test:

// generate-users.groovy  -  usage: groovy generate-users.groovy 500 > data/users.csv
int count = (args ? args[0] : '500') as int
println 'username,password'
(1..count).each { i ->
    String id = String.format('%04d', i)
    println "reader${id},change-me-${id}"   // placeholder credentials for a disposable test database
}

The same accounts must exist in the test environment; the matching seed step (an SQL script or a loop over a registration endpoint) belongs to the environment set-up, run before JMeter.

In a setUp Thread Group

When the data depends on the run (a seed, a run id) it can be generated by JMeter itself in a setUp Thread Group, which finishes before the regular Thread Groups start (see Thread Groups: Concurrent Users & Ramp-up). The CSV Data Set Config of the main group opens its file in its first iteration, which is after the setUp group has ended, so a file written in setUp is complete when it is read.

The JSR223 Sampler below, in a setUp Thread Group with one thread and one iteration, shuffles search-terms.csv with a seed taken from the seed property and writes data/generated/search-terms-run.csv. A fixed seed gives a repeatable order for comparisons between builds; a new seed gives a different order per run without any work during the test. Relative paths are resolved against the folder of the test plan with FileServer.getBaseDir():

import org.apache.jmeter.services.FileServer

def base = new File(FileServer.getFileServer().getBaseDir())
def source = new File(base, 'data/search-terms.csv')
def target = new File(base, 'data/generated/search-terms-run.csv')
target.parentFile.mkdirs()

List<String> lines = source.readLines('UTF-8')
String header = lines.head()
List<String> rows = lines.tail()
long seed = (props.getProperty('seed') ?: '42') as long
Collections.shuffle(rows, new Random(seed))

target.withWriter('UTF-8') { w ->
    w << header << '\n'
    rows.each { w << it << '\n' }
}
SampleResult.setResponseMessage("wrote ${rows.size()} terms with seed ${seed}")

Run it from the command line with a seed:

jmeter -n -t bookshelf-load.jmx -Jseed=20261014 -Jthreads=50 -Jrampup=60 -Jduration=660 -l results/run.jtl

The CSV Data Set Config for search terms then points at data/generated/search-terms-run.csv. Keep data/generated/ out of version control. Script caching, the props and SampleResult bindings and the rule against ${...} inside cached scripts are covered on Scripting with JSR223 & Groovy.

CSV files in distributed runs

In distributed testing every server runs the whole plan, and the client does not send data files. The remote testing chapter says so directly: files used by the test must be made available in the appropriate directory on each server. The component reference adds that a relative Filename must exist on the server in the correct directory relative to where the JMeter server was started, and that absolute paths only work if every server has the same layout. Three ways to handle it:

Approach How When

Same copy on every node

Copy data/ to each server next to the plan.

Reusable values such as search terms. Each server reads the file from its first line, so with three servers every term is searched three times as often at the same moment, which is usually acceptable.

Partitioned copies

Split the file so that each server gets a disjoint part, under the same name or under a per-server name.

Values that must be unique across the whole test: accounts that must not log in twice at once, one-time ids. The manual suggests exactly this: if each server needs unique ids, divide them between the data files.

Per-server property

${__P(node,0)} in the Filename, with node=1, node=2… in each server’s user.properties.

Partitioned data with one plan and no renaming. The manual describes defining different property values on each server in user.properties or system.properties.

A partitioning step for three servers, keeping the header line in every part (the round-robin split also keeps the distribution of popular and rare values similar in each part):

awk -v n=3 'NR==1 { for (i = 1; i <= n; i++) print > ("data/users-" i ".csv"); next }
            { print > ("data/users-" ((NR - 2) % n + 1) ".csv") }' data/users.csv
wc -l data/users-*.csv

Output for a 500-account file:

     168 data/users-1.csv
     168 data/users-2.csv
     167 data/users-3.csv
     503 total

Each server gets its part and a user.properties entry (node=1 on the first server, and so on); the CSV Data Set Config uses data/users-${__P(node,0)}.csv as its Filename. Remember also that every server multiplies the load: 50 threads on three servers is 150 users, so the row calculations of Worked numbers: how many rows a run needs apply per server. Counters and Random Variables also run independently on each server, so a global Counter gives the same LN-000001 on every server; prefix it with ${__P(node,0)} when values must be unique across the test.

Test-data hygiene

The manual covers the mechanics; the methodology comes from the books, and the Pro Apache JMeter chapter on workload modelling (Pro ch. 11) is the basis of the following rules:

  • Production-like volume. The database under test should hold roughly as many rows as production: a search over 500 books is served from memory, a search over two million is not. Seed the catalogue to scale, not only the accounts the test logs in with.

  • Production-like distribution. Repeat popular search terms, skew book popularity, and give accounts a realistic number of existing loans. The share of the Bookshelf mix (60 % search-only, 30 % browse and loan, 10 % login-heavy) is a property of the controllers; the share of popular versus rare values is a property of the data files.

  • Obfuscated or synthetic, never real. Do not copy production personal data into a test environment or into a data file in a repository. Generate synthetic records, or anonymise a production extract so that it keeps its shape (lengths, character sets, distributions) without identifying anyone. Credentials in data files are placeholders for accounts that only exist in the test database.

  • Reset between runs. A load test changes the data: loans are created, books become unavailable, tables and indexes grow. A second run against the changed database is not comparable with the first. Restore a database snapshot or re-run the seed before each run, or clean up in a tearDown Thread Group, which runs after the regular groups.

  • Enough data for the run. Size each file from the iteration count, as in Worked numbers: how many rows a run needs, and choose recycle or stop on EOF deliberately.

  • Version the data with the plan. Keep data/ and the generator scripts in the same repository as the .jmx, so a result can be reproduced with the exact data that produced it, and exclude generated and partitioned files.

  • Mock what you do not own. Third-party calls (payment, e-mail) should hit a stub with its own test data, not a real provider; load is only ever aimed at localhost or at systems you own.

Pitfalls

  • Header read as data. Variable Names filled in and Ignore first line left unchecked on a file that has a header: one iteration in every pass logs in as username.

  • Plan not saved. A relative path in a new, unsaved plan has no plan folder to resolve against. Save the plan before testing the data set.

  • Two spellings of one file. users.csv in one element and ./users.csv in another give two independent read positions, so the same accounts are used twice.

  • Encoding. A file saved as ISO-8859-1 or UTF-16 by a spreadsheet and read with another encoding garbles accented terms (pérez). Save as UTF-8 and set File Encoding to UTF-8 explicitly; an empty field depends on the JVM’s platform default. A byte order mark is handled: in the 5.6.3 source the file is opened through a BOM-aware stream that skips it, and with an empty File Encoding the BOM’s charset is used.

  • Short lines. A line with fewer values than variables leaves the missing variables with their previous value: the request silently reuses data from the last iteration.

  • Blank lines. __CSVRead treats the first blank line as the end of the file, so a blank line in the middle hides everything after it. Keep data files free of blank lines for every reader.

  • <EOF> in requests. No recycle and no stop: every request after the end of the file sends <EOF>.

  • Fewer lines than threads. With the default mode, two threads use the same account at once.

  • A random function in User Defined Variables. Evaluated once for the whole test, so every thread uses the same value.

  • Thread number in file names for __StringFromFile. The functions reference warns that it leads to unpredictable behaviour, because the function instance is shared and the file is reopened by whichever thread needs the next line. Use Current thread sharing with test${__threadNum}.csv in a CSV Data Set Config instead.

  • __threadNum in configuration elements. It does not work in configuration elements such as User Defined Variables, which run from a separate thread.

  • Missing file. Since 3.0 a CSV Data Set Config that points at a file that does not exist stops the test; in a distributed run this happens on the server that lacks the file.

  • Data under the wrong controller. A CSV Data Set Config inside a Loop Controller advances once per loop pass, not once per Thread Group iteration. Move it to the level whose iteration should change the value.

From the books to 5.6 to 6.0

Topic 2.x / 3.0 (the books) 5.6.3 and planned 6.0

CSV Data Set Config

The same element and fields; header lines are supported since 2.4, sharing modes since 2.x.

Since 3.0 a missing file stops the test; since 3.1 file and variable names are trimmed and the log states when a thread exits at EOF; since 5.0 the file name is stored as typed, keeping forward slashes. 5.6 fixed skipped rows with the Open Model Thread Group. No change is planned for 6.0.

Counter

The same element.

3.3 and 5.3 clarified the labels and documentation (Starting value, Maximum value, Exported Variable Name); the behaviour is unchanged.

Random Variable

The same element; minimum equal to maximum logged an error.

Since 3.2 minimum may equal maximum.

User Parameters

The same element.

Since 3.2 rows can be moved up and down.

Functions

_StringFromFile (single underscore) still accepted.

_StringFromFile was dropped in 3.3; use __StringFromFile. Date functions changed formatter in 5.5 (see the properties and functions page).

Generating data in scripts

BeanShell or BSF pre-processors.

BSF was deprecated in 3.1 (still shipped, hidden from the menus) and BeanShell is discouraged; use JSR223 with Groovy, preferably before the run. 6.0 plans to move to Groovy 5.

Source: Changes history, Changes and Apache JMeter source (xdocs/changes.xml on master)

References