Skip to main content

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 seeWhat it isExample
${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 scriptGroovy code, not ${} syntax
props.get("name")Reading a property inside a JSR223 scriptGroovy 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.

VariableProperty
ScopeOne thread onlyEntire JVM - every thread shares it
Each user getsTheir own separate copyThe same single value
Set byUser 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 forThe life of one thread, carried across iterations unless overwrittenThe whole test run
Typical useTest data and correlated values - tokens, usernames, IDsTest 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:

SourceSetsCovered in
User Defined VariablesSame value for every threadParameterization below
CSV Data Set ConfigA different row per thread / iterationParameterization below
Extractors (JSON, Regex, CSS)A value pulled out of a server responseCorrelation below
JSR223 vars.put()Anything you compute in GroovyHandling Timestamps below
FunctionsGenerated 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, __P returns 1 - not empty, not an error. ${__P(threads)} with no -Jthreads flag runs your load test with one user, finishes quickly, reports green, and tells you nothing.

In the Thread GroupRun with -Jthreads=300Run with no flag passed
${__P(threads,10)}300 users10 users - your stated default
${__P(threads)}300 users1 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 the s) 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 .jmx documents 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​

FlagSets
-JA JMeter property on the local machine - read with ${__P()}
-DA Java system property (e.g. proxy settings, SSL options)
-GA 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 - use vars.putObject() for lists, maps, or other objects. props is a standard java.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 returns 1, 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 -J stay on the controller. Use -G to push a property out to the workers. See Section 12 - Distributed Testing.

Correlation​

What is Correlation?​

Correlation is the process of:

  1. Identifying dynamic values in the recorded script (values that change every time you run the flow)

  2. Finding where those values first appear in a server response

  3. Extracting them from that response

  4. 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:

  1. Look through the recorded requests in JMeter - spot any values that look dynamic (long random strings, tokens, encoded values, timestamps)

  2. Copy that suspicious value

  3. In Fiddler, search for it (Ctrl+F) - Fiddler will highlight (yellow) all sessions that contain that value

  4. Look at the earliest highlighted session - that's likely where the value first appeared

  5. 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
  6. In JMeter, add an extractor (Post-Processor) to the corresponding request to capture that value

  7. Replace the hardcoded value in subsequent requests with the extracted variable ${variableName}

Extractors​

Use extractors (Post-Processors) to capture dynamic values from responses:

ExtractorWhen to Use
JSON ExtractorResponse is JSON (most APIs)
Regular Expression ExtractorResponse is HTML, or value is in headers, or JSON extraction is too complex
CSS/jQuery ExtractorResponse 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 CaseApproach
Different data per user (credentials, IDs)CSV Data Set Config
Same value for all users, but changes per environmentUser Defined Variables
Value extracted from a previous responseCorrelation (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_FOUND so you can easily spot extraction failures in View Results Tree