Correlation and Parameterization
After recording, the script has hardcoded values everywhere - tokens, session IDs, timestamps, and other dynamic values that change every session. The script will fail on replay if these are not handled. This step makes the script dynamic.
Variables and Properties
Before correlation and parameterization, it helps to know what ${...} actually means. You will see it on nearly every page from here on, and there is more than one thing hiding behind that syntax.
The Syntax You Will See
| What you see | What it is | Example |
|---|---|---|
${name} | Variable - a value held for one thread (one virtual user) | ${token}, ${username} |
${__P(name,default)} | Property - a value shared by the whole JVM, usually passed from the command line | ${__P(threads,10)} |
${__name(args)} | Function - built-in logic, re-evaluated every time it is read | ${__time(,)}, ${__Random(1,100)} |
vars.get("name") | Reading a variable inside a JSR223 script | Groovy code, not ${} syntax |
props.get("name") | Reading a property inside a JSR223 script | Groovy code, not ${} syntax |
Anything starting with ${__ (two underscores) is a function, not a variable. That is the fastest way to tell them apart at a glance.
Variables vs Properties
This is the distinction that trips people up most - they look similar but live in completely different places.
| Variable | Property | |
|---|---|---|
| Scope | One thread only | Entire JVM - every thread shares it |
| Each user gets | Their own separate copy | The same single value |
| Set by | User Defined Variables, CSV Data Set Config, extractors, vars.put() | -J on the command line, jmeter.properties, user.properties, props.put(), ${__setProperty()} |
| Read with | ${name} or vars.get("name") | ${__P(name,default)} or props.get("name") |
| Lives for | The life of one thread, carried across iterations unless overwritten | The whole test run |
| Typical use | Test data and correlated values - tokens, usernames, IDs | Test configuration - thread count, duration, ramp-up, environment |
Rule of thumb: if the value should be different per user, it is a variable. If it is a knob you turn for the whole test, it is a property.
Where Variables Come From
There are only a handful of sources, and the rest of this section covers the two main ones:
| Source | Sets | Covered in |
|---|---|---|
| User Defined Variables | Same value for every thread | Parameterization below |
| CSV Data Set Config | A different row per thread / iteration | Parameterization below |
| Extractors (JSON, Regex, CSS) | A value pulled out of a server response | Correlation below |
JSR223 vars.put() | Anything you compute in Groovy | Handling Timestamps below |
| Functions | Generated on the fly, no variable needed | ${__time(,)}, ${__UUID()}, ${__threadNum} |
Properties: Controlling the Test From Outside
Properties exist so you can change how the test runs without editing the .jmx file. The usual pattern is to put ${__P()} in the Thread Group instead of hardcoded numbers:
- Number of Threads:
${__P(threads,10)} - Ramp-up Period:
${__P(rampup,60)} - Duration:
${__P(duration,600)}
Then pass the real values at run time:
jmeter -n -t test.jmx -Jthreads=300 -Jrampup=300 -Jduration=1800 -l results.jtl
Always Set a Default Value
The second argument in ${__P(name,default)} is the value used when nothing is passed on the command line. It is optional in JMeter's syntax, but leaving it out is the single most common way to get a test that looks like it ran fine and did nothing useful.
Important: If no default is supplied,
__Preturns1- not empty, not an error.${__P(threads)}with no-Jthreadsflag runs your load test with one user, finishes quickly, reports green, and tells you nothing.
| In the Thread Group | Run with -Jthreads=300 | Run with no flag passed |
|---|---|---|
${__P(threads,10)} | 300 users | 10 users - your stated default |
${__P(threads)} | 300 users | 1 user - silent, easy to miss |
Setting a default gives you three things:
- The script still opens and runs in the GUI. You can hit play while building the script without passing flags every time.
- A forgotten or misspelled flag degrades, it doesn't break.
-Jthread=300(missing thes) is not an error - JMeter just doesn't find the property. With a default you get your baseline load; without one you get a single user. - The
.jmxdocuments its own intended load. Anyone opening the file sees the shape of the test without hunting for the batch script that runs it.
Tip: Pick defaults that are safe to run by accident - a small smoke-test load like 10 users, not 500. The default is what runs when someone opens your script and presses play.
See Section 9 - Running from CLI and Section 14 - Automation and Batch for how this fits into scripted runs.
Command-Line Flags Worth Knowing
| Flag | Sets |
|---|---|
-J | A JMeter property on the local machine - read with ${__P()} |
-D | A Java system property (e.g. proxy settings, SSL options) |
-G | A JMeter property that is also sent to remote workers in distributed mode |
Reading and Writing Both in JSR223
// Variable - visible to this thread only
vars.put("orderId", "12345")
String id = vars.get("orderId")
// Property - visible to every thread in the JVM
props.put("sharedToken", "abc123")
String shared = props.get("sharedToken")
Note:
vars.put()only accepts Strings - usevars.putObject()for lists, maps, or other objects.propsis a standardjava.util.Properties, which is why it is the usual way to hand a value from one thread to another.
Gotchas
- Always give
${__P()}a default. With no default it returns1, so a missing flag silently runs a one-user test. See Always Set a Default Value above. - A literal
${something}in the request means the value was never set - the variable does not exist, so JMeter leaves the text as-is. See Section 7 - Unresolved Variables. - Names are case-sensitive.
${Token}and${token}are two different variables. - You cannot nest directly.
${${prefix}}does not work - use${__V(user_${index})}when the variable name itself is built from another variable. - No spaces in variable names.
${user name}will not resolve. - Variables never cross machines. In distributed testing each worker runs its own threads with its own variables, and properties set with
-Jstay on the controller. Use-Gto push a property out to the workers. See Section 12 - Distributed Testing.
Correlation
What is Correlation?
Correlation is the process of:
-
Identifying dynamic values in the recorded script (values that change every time you run the flow)
-
Finding where those values first appear in a server response
-
Extracting them from that response
-
Passing them to subsequent requests that need them
Common dynamic values:
- Authentication tokens / JWT
- Session IDs
- CSRF tokens
- Request verification tokens
- Timestamps generated by the server
- Dynamic IDs (order ID, transaction ID)
How to Find Dynamic Values Using Fiddler
This is where the dual recording pays off. Open the saved Fiddler .saz file and the JMeter .jmx side by side.
Note: Using mitmproxy instead of Fiddler? The equivalent steps are in Correlation with mitmproxy.
Steps:
-
Look through the recorded requests in JMeter - spot any values that look dynamic (long random strings, tokens, encoded values, timestamps)
-
Copy that suspicious value
-
In Fiddler, search for it (Ctrl+F) - Fiddler will highlight (yellow) all sessions that contain that value
-
Look at the earliest highlighted session - that's likely where the value first appeared
-
Check where the value lives in that response:
- Response body → use the SyntaxView tab to find it
- Response headers → check the Raw tab
- Cookies → check the Cookies tab
-
In JMeter, add an extractor (Post-Processor) to the corresponding request to capture that value
-
Replace the hardcoded value in subsequent requests with the extracted variable
${variableName}
Extractors
Use extractors (Post-Processors) to capture dynamic values from responses:
| Extractor | When to Use |
|---|---|
| JSON Extractor | Response is JSON (most APIs) |
| Regular Expression Extractor | Response is HTML, or value is in headers, or JSON extraction is too complex |
| CSS/jQuery Extractor | Response is HTML and value is in a specific element |
JSON Extractor Example
If the response is:
{
"data": {
"token": "abc123xyz"
}
}
Configuration:
-
Variable name:
token -
JSON Path expression:
$.data.token
Regular Expression Extractor Example
If the response body contains:
<input name="__RequestVerificationToken" value="CfDJ8NrAkS..." />
Configuration:
-
Variable name:
verificationToken -
Regular Expression:
name="__RequestVerificationToken" value="(.+?)" -
Template:
$1$ -
Match No.:
1
Handling Timestamps
Some requests require a current timestamp in the payload (e.g., requestTime, createdAt). The recorded value is hardcoded and will be outdated on replay.
Use a JSR223 PreProcessor (Groovy) to generate the timestamp dynamically before the request is sent:
// Epoch milliseconds
vars.put("timestampMs", String.valueOf(System.currentTimeMillis()))
// Epoch seconds
vars.put("timestampSec", String.valueOf(System.currentTimeMillis() / 1000))
// Formatted date-time (e.g., 2026-02-12T10:30:00.000Z)
import java.time.Instant
import java.time.format.DateTimeFormatter
vars.put("timestampISO", Instant.now().toString())
Then use ${timestampMs}, ${timestampSec}, or ${timestampISO} in the request body.
Tip: You can also use JMeter's built-in function
${__time(,)}for epoch milliseconds or${__time(yyyy-MM-dd'T'HH:mm:ss.SSS'Z',)}for formatted timestamps. But for complex date logic (e.g., date +7 days, specific timezone), a JSR223 PreProcessor gives you more control.
Using the Extracted Variable
Once extracted, use ${variableName} in subsequent requests. For example:
- Header:
Authorization: Bearer ${token} - Body:
"token": "${verificationToken}"
Parameterization
What is Parameterization?
Parameterization replaces hardcoded static values with variable data so each virtual user can use different inputs. Without it, all users send the same data (same username, same product ID).
CSV Data Set Config
The most common way to parameterize. Reads data from a CSV file and assigns each row to a thread.
Example CSV file (testdata/users.csv):
username,password
user001,Pass@123
user002,Pass@123
user003,Pass@123
CSV Data Set Config settings:
-
Filename: path to the CSV file (relative or absolute)
-
Variable Names:
username,password -
Delimiter:
, -
Recycle on EOF: True (start over when all rows are used)
-
Stop thread on EOF: False
-
Sharing mode: All threads (default)
Then use ${username} and ${password} in your requests.
User Defined Variables
For values that are the same for all users but might change between environments (e.g., base URL, port):
-
Name:
baseUrl→ Value:https://staging.example.com -
Name:
port→ Value:443
Use in HTTP Request Defaults or directly in samplers.
When to Use CSV vs User Defined Variables
| Use Case | Approach |
|---|---|
| Different data per user (credentials, IDs) | CSV Data Set Config |
| Same value for all users, but changes per environment | User Defined Variables |
| Value extracted from a previous response | Correlation (extractor) |
Advanced: Dynamic Forms
Some applications have forms that differ based on the user - different fields, different options, different structures depending on the user's profile, role, or account data.
The Problem
-
Extracting each form field individually is not reliable since the fields are not consistent across users
-
Using HTTP Form Manager works locally - it can capture and replay the form structure per user
-
However, HTTP Form Manager breaks in distributed testing due to serialization issues across remote machines
The Solution
For dynamic forms in distributed testing, you need to develop a script (e.g., using JSR223/Groovy) to handle the form data programmatically rather than relying on JMeter's built-in form manager. This could involve:
- Capturing the entire form response and parsing it dynamically
- Building the form submission body in a script based on what was received
- Storing the form data in a format that survives distributed execution
Note: This is an advanced scenario. If you're running locally, HTTP Form Manager is the simpler option. Only move to a scripted approach when you need distributed testing with dynamic forms.
Tips
-
Correlate first, parameterize second - fix the dynamic values before worrying about test data
-
Start with the first failure - run the script, find where it breaks, fix that correlation, run again. Repeat until the flow completes
-
Fiddler is your best friend - the ability to search across all requests and responses makes finding dynamic values much faster than hunting in JMeter alone
-
Name your variables meaningfully -
${authToken}is better than${var1} -
Check the "default value" in extractors - set a default like
NOT_FOUNDso you can easily spot extraction failures in View Results Tree