Correlation & Extractors

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 (latest release on 2026-10-14; it runs on Java 8 and later, Java 17 or later is recommended), with the differences planned for JMeter 6.0 noted where they matter. JMeter 6.0 is not released: the planned changes come from xdocs/changes.xml on the master branch, need Java 17 or later (Java 21 recommended) and are described as planned, not as shipped behaviour. Every extractor on this page ships with core JMeter, so no plugin is needed: the old jp@gc JSON Path Extractor plugin that the books use has been replaced by the core JSON Extractor (3.0), JSON JMESPath Extractor (5.2) and Boundary Extractor (4.0). Field names, menu paths and behaviour come from the official component reference and the regular expressions chapter of the user manual. Where the manual is silent about which variables an extractor sets, the page says what the 5.6.3 source code does and names the class it was read from.

A test plan that replays the same requests with the same values works for one user, once. Real APIs hand out values that change on every call: a login returns a fresh JWT, POST /api/loans returns the id of a loan that did not exist a second ago, an HTML form carries a CSRF token valid for one session. Correlation is the work of capturing such a value from one response and sending it in a later request. JMeter does it with extractors, post-processors that read a response and write JMeter variables. This page explains how to find the values to correlate, every extractor and all its fields, exactly which variables each one creates (the -1 / _matchNr rules differ between them), and the supporting techniques: defaults that make failures visible, ForEach loops over all matches, the regular expression syntax and the Debug elements. It ends with the Bookshelf example: the login token and the new loan id.

What correlation is

A dynamic value is any value in a request that the server generated, or that depends on server state, and that is therefore different for every user, session or iteration. In a REST or web application the usual ones are:

Kind Bookshelf example Where it appears

Authentication token (JWT, opaque bearer token)

token in the POST /api/auth/login response

Response body; sent back in an Authorization: Bearer header

Created resource id

The new loan’s id from POST /api/loans

Response body and often the Location response header; sent back in a URL path

Selected item id

A bookId picked from the search results

Response body (a list); sent back in a URL path

CSRF / anti-forgery token

A hidden _csrf input in a server-rendered form (not part of the JSON API; shown for completeness)

HTML body or a cookie; sent back as a form field or header

Session id

A JSESSIONID cookie

Set-Cookie header; handled by the HTTP Cookie Manager, not by an extractor

Pagination cursor, ETag, version number

An ETag for an optimistic-locking update

Response header or body; sent back in a request header or body

Without correlation a recorded plan sends the token, ids and CSRF values that existed when it was recorded. The requests then fail (401, 403, 404, 409) or, worse, succeed against the same single record for every virtual user, which measures a cache rather than the system. A load test whose requests do not do the real work is not a load test.

Cookies are the exception: the HTTP Cookie Manager stores Set-Cookie values per thread and sends them back automatically, so session cookies need no extractor. Extractors are needed for every value that travels in a body, a URL or a custom header.

Finding the values to correlate

Three techniques find correlation candidates, and they work best together.

Compare two recordings. Record the same journey twice, ideally with two different users, with the HTTP(S) Test Script Recorder (see Recording & cURL Import). Save each as a .jmx file and compare them with any diff tool. Values that differ between the two recordings in the same request are dynamic: tokens, ids, timestamps, nonces. Values that are identical are either constants or data that should come from a CSV file (see Parameterization & Test Data). Then, for each dynamic value, find the earliest response that contains it: that response is where the extractor goes.

Search in the View Results Tree. Run one user (Thread Group with 1 thread, 1 loop, or the Validate command) with a View Results Tree listener (Add → Listener → View Results Tree). Take the dynamic value from a request that failed and type it into the listener’s Search field: the listener highlights every sample and response that contains it, so the first response holding the value is found in seconds. The component reference notes that this search uses the Java regular expression engine, not the ORO engine of the Regular Expression Extractor, and that it searches the visible text, so the Text and HTML views can give different results.

Read the API contract. For an API described by OpenAPI or a GraphQL schema, the dynamic fields are documented: the login response schema has a token, the create-loan response has an id. The contract is faster than any diff.

The View Results Tree also has a renderer per extractor type, chosen from the drop-down at the bottom of its left panel, to try an expression against a real response before putting it in the plan: Regexp Tester, CSS/JQuery Tester, XPath Tester (and XPath2 Tester), JSON Path Tester, JSON JMESPath Tester and Boundary Extractor Tester. The Regexp Tester uses the same engine as the Regular Expression Extractor and prints every match with its groups, as Match[1][0] for the whole first match and Match[1][1] for its first group.

The View Results Tree keeps responses in memory and must not be used during a load test (its own documentation says so); use it for building and debugging only, then disable it.

How extraction fits in the plan

An extractor is a post-processor. After a sampler runs, JMeter runs the post-processors in its scope, in tree order, before the assertions and the listeners (see Test Plan Elements, Execution Order & Scoping). The extractor reads the sampler’s result (body, headers, URL, code or message, depending on its settings) and writes one or more JMeter variables. Variables belong to the thread, so each virtual user has its own token and its own loanId. The next sampler then references them as ${token} or ${loanId}, and JMeter substitutes the values just before that sampler runs.

sequenceDiagram autonumber participant VU as Virtual user (thread) participant API as Bookshelf API participant EX as JSON Extractor participant VARS as Thread variables VU->>API: POST /api/auth/login with username and password API-->>VU: 200 OK and body with the token field VU->>EX: post-processors run on this sample EX->>EX: evaluate the JSONPath expression on the body EX->>VARS: put token, or the default TOKEN_NOT_FOUND VU->>VARS: next sampler resolves the token variable VARS-->>VU: current token value of this thread VU->>API: POST /api/loans with header Authorization Bearer token API-->>VU: 201 Created and body with the new loan id VU->>EX: a second extractor reads the loan id EX->>VARS: put loanId VU->>API: GET /api/loans/loanId with the same token

Three consequences of that model shape everything below:

  • Place each extractor as a child of the sampler whose response holds the value. The component reference says that post-processors apply to every sampler in their scope. An extractor placed directly under the Thread Group runs after every sampler of the thread and overwrites the variable each time, usually with the default value. The JSON Extractor’s documentation states explicitly that it must be placed as a child of the sampler.

  • Extractors run before assertions. An extractor cannot see whether an assertion failed, and the sample status it sees is the sampler’s own. To act on a failed extraction, add an assertion on the variable or on the response, and a Result Status Action Handler (described below) to decide what the thread does next.

  • Extractors apply to the parent sample only, unless told otherwise. The default Apply to setting is Main sample only. Sub-samples (embedded resources, redirects, children of a Transaction Controller in parent mode) are only searched when Apply to says so.

Fields shared by most extractors

Two settings appear in almost every extractor and behave the same way in all of them.

Apply to. Chooses which sample results the extractor reads.

Option Meaning JMX value (Sample.scope)

Main sample only

Only the sampler’s own result. The default; JMeter does not write the property for it.

parent (or absent)

Sub-samples only

Only the sub-samples: embedded resources, followed redirects, the children of a Transaction Controller that generates a parent sample, the messages of a Mail Reader.

children

Main sample and sub-samples

Both, main sample first.

all

JMeter Variable Name to use

The content of a JMeter variable instead of a response; the variable name goes in the text field next to the option. Useful to extract from a value an earlier extractor captured (a JSON fragment, a decoded header).

variable, with the name in Scope.variable

When several samples qualify, the Regular Expression, CSS Selector and Boundary Extractors count matches across them in order. The component reference gives the example of a main sample and three sub-samples with one match each: with Match No. 3 and Sub-samples only the extractor uses the third sub-sample; with Main sample and sub-samples it uses the second sub-sample, because the first match is the main sample. With Match No. 0 or negative every qualifying sample is processed; with a positive number matching stops as soon as enough matches have been found.

Match No. (0 for Random). Chooses which match is stored when the expression matches several times.

Value Effect

0

A random match. Useful to pick a random book from a search result per iteration; harmful when the value must be deterministic.

N > 0

The N-th match. If there are fewer than N matches, the default value is used.

-1 (any negative number)

All matches, stored as numbered variables name_1, name_2 and so on, for a ForEach Controller or for an index picked with a function. Which other variables are written differs between extractors: see Which extractor sets what: the -1 and _matchNr rules.

Always fill Match No. explicitly. The JSON Extractor documents that an empty field means 0 for every expression, and the 5.6.3 JMESPath code reads an empty field as 0 too: a random pick, which is rarely what a correlation needs. An empty field never means "the first match".

JSON Extractor

The JSON Extractor (class JSONPostProcessor) evaluates one or more JSONPath expressions against a JSON response and stores the results in variables. It uses the Jayway JsonPath library. It is the natural choice for REST APIs like Bookshelf: it is easy to read, it understands nesting and arrays, and one element can extract several values at once.

Add it with right-click the sampler → Add → Post Processors → JSON Extractor.

Field (GUI label) Meaning Required

Name

Descriptive name shown in the tree.

No

Apply to

Main sample only, Sub-samples only, Main sample and sub-samples, or JMeter Variable Name to use (see above).

Yes

Names of created variables

Semicolon-separated variable names, one per JSONPath expression. JMX: JSONPostProcessor.referenceNames.

Yes

JSON Path expressions

Semicolon-separated JSONPath expressions; their number must match the number of variable names. JMX: JSONPostProcessor.jsonPathExprs.

Yes

Match No. (0 for Random)

Semicolon-separated list, one number per expression: 0 random (the documented default), -1 all results as name_N, N the N-th result (the default value if there are fewer than N). If left empty, 0 is used for every expression. JMX: JSONPostProcessor.match_numbers.

No

Default Values

Semicolon-separated defaults, one per expression, used when the expression returns no result. JMX: JSONPostProcessor.defaultValues.

No

Compute concatenation var (suffix _ALL)

When ticked and the match number is -1, also stores all results joined with a comma in name_ALL. JMX: JSONPostProcessor.compute_concat (default false).

No

The variable names, expressions and default values must have the same number of entries. If they do not, 5.6.3 logs "Number of JSON Path variables must match number of default values and json-path expressions" and the extractor fails for that sample. Two consequences follow:

  • With three expressions, write three defaults, even if they are the same: NOT_FOUND;NOT_FOUND;NOT_FOUND.

  • A trailing empty default does not work in 5.6.3. NOT_FOUND; is split into one entry, not two, so the counts no longer match. Use an explicit placeholder in the last position. The planned 6.0 change log lists a fix that supports trailing empty default values.

JSONPath in five minutes

The component reference links to the Jayway JsonPath syntax. The parts used in load tests:

Expression Selects

$.token

The token field of the root object.

$.loan.id or $['loan']['id']

A nested field.

$.items[0].id

The id of the first array element.

$.items[*].id

The id of every element (several results).

$..id

Every id at any depth (deep scan; costly on large documents).

$.items[?(@.copiesAvailable > 0)].id

A filter: ids of books with copies available.

$.items.length()

A function: the size of the array.

A JSONPath result that is an object or an array is stored as JSON text; a string, number or boolean is stored as its text. A null value gives the default value (fixed in 5.5).

Variables set by the JSON Extractor

The component reference only documents name_N and name_ALL. The 5.6.3 source (JSONPostProcessor) is more precise, and the differences matter when a ForEach Controller or an If Controller reads name_matchNr:

Situation Variables written

Match No. N > 0, at least N results

name = the N-th result; name_matchNr = number of results.

Match No. N > 0, fewer than N results

name = default; name_matchNr = number of results.

Match No. 0, at least one result

name = a random result. name_matchNr is not written (it is cleared first, so it is undefined).

Match No. -1, one or more results

name_1 …​ name_N; name_matchNr = N; name_ALL if the concatenation option is ticked (also for a single result since 3.2). name itself is not touched: it keeps whatever value it had before.

Expression returns no result (any match number)

name = default; name_matchNr = 0; name_ALL = empty string if -1 and the option is ticked.

Response body empty, or the source variable missing

name = default only.

Before each run the extractor removes the previous name_matchNr and name_1, name_2…​ of the same thread, so stale numbered values from the previous iteration do not leak into the next one.

Example: the token and the search results in one extractor each

The Bookshelf login returns {"token":"<jwt>","tokenType":"Bearer","expiresIn":900} and the search returns {"items":[{"id":42,"title":"...","copiesAvailable":3},...],"total":17}.

<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Extract token">
  <stringProp name="JSONPostProcessor.referenceNames">token;tokenTtl</stringProp>
  <stringProp name="JSONPostProcessor.jsonPathExprs">$.token;$.expiresIn</stringProp>
  <stringProp name="JSONPostProcessor.match_numbers">1;1</stringProp>
  <stringProp name="JSONPostProcessor.defaultValues">TOKEN_NOT_FOUND;0</stringProp>
</JSONPostProcessor>
<hashTree/>
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Extract book ids">
  <stringProp name="JSONPostProcessor.referenceNames">bookId</stringProp>
  <stringProp name="JSONPostProcessor.jsonPathExprs">$.items[*].id</stringProp>
  <stringProp name="JSONPostProcessor.match_numbers">-1</stringProp>
  <stringProp name="JSONPostProcessor.defaultValues">NO_BOOK</stringProp>
  <boolProp name="JSONPostProcessor.compute_concat">true</boolProp>
</JSONPostProcessor>
<hashTree/>

After a search with three hits the thread holds bookId_1=42, bookId_2=7, bookId_3=19, bookId_matchNr=3 and bookId_ALL=42,7,19. After a search with no hit it holds bookId=NO_BOOK and bookId_matchNr=0, and no bookId_1.

JSON JMESPath Extractor

The JSON JMESPath Extractor (class JMESPathExtractor, added in 5.2) does the same job with JMESPath, a JSON query language with a complete ABNF grammar and specification, so expressions behave the same in every JMESPath implementation (the AWS CLI uses it, for instance). It handles one expression per element, unlike the JSON Extractor.

Add it with Add → Post Processors → JSON JMESPath Extractor.

Field (GUI label) Meaning Required

Name

Descriptive name shown in the tree.

No

Apply to

Main sample only, Sub-samples only, Main sample and sub-samples, or JMeter Variable Name to use.

Yes

Names of created variables

The variable name (one only; the label is shared with the JSON Extractor). JMX: JMESExtractor.referenceName.

Yes

JMESPath expressions

One JMESPath expression. JMX: JMESExtractor.jmesPathExpr.

Yes

Match No. (0 for Random)

0 random, -1 all results as name_N, N the N-th result (default value if fewer). The component reference calls -1 the default, but the 5.6.3 code treats an empty field as 0: always fill it. JMX: JMESExtractor.matchNumber.

No

Default Values

The value stored when nothing matches. JMX: JMESExtractor.defaultValue.

No

An expression that returns a JSON array is split into its elements, so items[*].id yields one result per book. Text results are stored without quotes; numbers, objects and arrays are stored as JSON text.

Variables. In 5.6.3 (JMESPathExtractor), name_matchNr is written whenever the expression returns something, whatever the match number (also for 0), and is 0 when it returns nothing. With -1, name_1 …​ name_N are written and name is left untouched; with no result name gets the default. An empty response sets only the default.

<JMESPathExtractor guiclass="JMESPathExtractorGui" testclass="JMESPathExtractor" testname="Pick an available book">
  <stringProp name="JMESExtractor.referenceName">availableBookId</stringProp>
  <stringProp name="JMESExtractor.jmesPathExpr">items[?copiesAvailable &gt; `0`].id</stringProp>
  <stringProp name="JMESExtractor.matchNumber">0</stringProp>
  <stringProp name="JMESExtractor.defaultValue">NO_AVAILABLE_BOOK</stringProp>
</JMESPathExtractor>
<hashTree/>

This picks a random book that can be borrowed. In JMESPath literals are written between backticks, so the number zero is `0`. The JSONPath equivalent is $.items[?(@.copiesAvailable > 0)].id.

JSON Extractor or JMESPath? Both are core, both parse the whole body. Choose the JSON Extractor when one element should extract several values, or when the team already knows JSONPath; choose JMESPath for its precise specification, projections and functions (length, sort_by, max_by…​). Do not use both on the same response for the same value.

Regular Expression Extractor

The Regular Expression Extractor (class RegexExtractor) applies a Perl-style regular expression to a chosen part of the result and builds the stored value from a template that refers to the capture groups. It works on any text: JSON when a JSONPath parser is overkill, HTML, headers, the URL, the response code. It is the oldest extractor and the one the books use for everything.

Add it with Add → Post Processors → Regular Expression Extractor.

Field (GUI label) Meaning Required

Name

Descriptive name shown in the tree.

No

Apply to

Main sample only, Sub-samples only, Main sample and sub-samples, or JMeter Variable Name to use.

Yes

Field to check

Which part of the result is searched: Body, Body (unescaped), Body as a Document, Request Headers, Response Headers, URL, Response Code or Response Message (table below). JMX: RegexExtractor.useHeaders.

Yes

Name of created variable

The variable name, refName below. Each group of the match is also stored as refName_g#. JMX: RegexExtractor.refname.

Yes

Regular Expression

The expression. It must contain at least one capture group ( ) unless the template uses $0$. Do not enclose it in / /. JMX: RegexExtractor.regex.

Yes

Template ($i$ where i is capturing group number, starts at 1)

How the value is built from the match: $1$ is group 1, $2$ group 2, $0$ the whole match; any other text is copied, so $1$-$2$ joins two groups with a dash. JMX: RegexExtractor.template.

Yes

Match No. (0 for Random)

0 random, N the N-th match, negative for all matches (used with the ForEach Controller). JMX: RegexExtractor.match_number.

Yes

Default Value

Stored in refName when the expression does not match. "No, but recommended": without it there is no way to tell a non-matching expression from an extractor that did not run or a misspelt variable. JMX: RegexExtractor.default.

No

Use empty default value

When ticked and Default Value is empty, refName is set to an empty string instead of being left unset, so ${refName} becomes empty rather than the literal text ${refName}. Useful for optional values. JMX: RegexExtractor.default_empty_value.

No

Field to check Searches JMX value

Body

The response body, without headers. The default.

false

Body (unescaped)

The body with every HTML escape code replaced, without regard to context. The manual warns that this option "highly impacts performances".

unescaped

Body as a Document

Text extracted by Apache Tika from a document response (PDF, Office files, see the Apache Tika section). Also costly.

as_document

Request Headers

The headers JMeter sent. May be absent for non-HTTP samplers.

request_headers

Response Headers

The headers received, one per line. May be absent for non-HTTP samplers.

true

URL

The sampled URL (after redirects, the final one).

URL

Response Code

For example 201.

code

Response Message

For example Created.

message

The JMX name useHeaders is historical: false means "use the body", true means "response headers", and the other options have their own values.

Variables set by the Regular Expression Extractor

This is the extractor whose variables the component reference documents in full, and the reference for the -1 rule.

Match No. 0 or N > 0, and a match is found:

  • refName = the template applied to the match;

  • refName_g0, refName_g1, …​ = the whole match and each group;

  • refName_g = the number of groups in the expression (excluding group 0).

Match No. 0 or N > 0, no match: refName = the default (only if a default is given or Use empty default value is ticked), and refName_g0, refName_g1, …​ and refName_g are removed.

Match No. negative (all matches):

  • refName_matchNr = the number of matches, which can be 0;

  • refName_1, refName_2, …​ = the template applied to each match;

  • refName_1_g0, refName_1_g1, …​ = the groups of each match;

  • refName = always the default value, and refName_g0, refName_g1…​ are not set.

refName_matchNr is written only in the negative mode; in the other modes it is removed. Numbered variables left over from a previous run with more matches are removed too.

Worked example. The search response contains three books and the extractor uses the regular expression "id":(\d+),"title":"([^"]+)", the template $1$, Match No. -1, the name book and the default NO_BOOK:

Variable Value

book

NO_BOOK (always the default in this mode)

book_matchNr

3

book_1, book_2, book_3

42, 7, 19

book_1_g0

"id":42,"title":"Dune"

book_1_g1, book_1_g2

42, Dune

With Match No. 1 instead, the variables are book=42, book_g0="id":42,"title":"Dune", book_g1=42, book_g2=Dune and book_g=2, and no book_matchNr.

Example: the new loan id from the Location header

POST /api/loans answers 201 Created with a Location: /api/loans/1001 header. A header is plain text, so a regular expression on Response Headers is the right tool:

<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Loan id from Location">
  <stringProp name="RegexExtractor.useHeaders">true</stringProp>
  <stringProp name="RegexExtractor.refname">loanIdFromHeader</stringProp>
  <stringProp name="RegexExtractor.regex">(?im)^location:\s*\S*/api/loans/(\d+)</stringProp>
  <stringProp name="RegexExtractor.template">$1$</stringProp>
  <stringProp name="RegexExtractor.default">LOANID_NOT_FOUND</stringProp>
  <stringProp name="RegexExtractor.match_number">1</stringProp>
  <boolProp name="RegexExtractor.default_empty_value">false</boolProp>
</RegexExtractor>
<hashTree/>

(?i) makes the match case-insensitive, because HTTP header names are case-insensitive (an HTTP/2 server sends location). (?m) (multi-line mode, below) makes ^ match at the start of every header line: the user manual’s regular expressions chapter describes ^ as matching only at the start of the whole text by default, and the response headers start with the status line, so the flag is stated explicitly. \S* accepts both a relative path and an absolute URL.

Regular expression syntax in JMeter

The rules come from the Regular Expressions chapter of the user manual.

Engine. By default JMeter uses Apache Jakarta ORO, a Perl 5 compatible engine. Since 5.5 the JMeter property jmeter.regex.engine switches to the JDK’s java.util.regex when set to any value other than oro. The property is read once at start-up, so set it in user.properties or with -J, never in the plan.

# user.properties: use the JDK regex engine instead of ORO (5.5 and later)
jmeter.regex.engine=java

No delimiters, embedded modifiers. Do not write /abc/i. Write (?i)abc. Modifiers can be placed anywhere and apply from that point on, so Match ExAct case or (?i)ArBiTrARY(?-i) case ignores case only in the middle part. An ORO bug prevents modifiers at the very end of an expression, where they would have no effect anyway.

Line modes. Single-line mode (?s) lets . match a newline as well. Multi-line mode (?m) makes ^ and $ match at the start and end of every line instead of only at the start and end of the whole text. The two are independent. Response headers and HTML bodies are multi-line, so these modes matter.

Meta-characters to escape with a backslash: ( ), [ ], { }, * + ?, ., \, |, ^ and $. ORO does not support \Q...\E quoting; the __escapeOroRegexpChars function escapes a value instead, for example ${__escapeOroRegexpChars(valueToEscape)}.

Supported Perl 5 extensions in ORO: (?#text) comments, (?:regexp) non-capturing groups, (?=regexp) positive look-ahead, (?!regexp) negative look-ahead, and the (?imsx) modifiers. Look-behind (?<=regexp) is not supported by ORO (it is by the Java engine).

Lazy and negated classes. The manual’s example extracts readme.txt from name="file" value="readme.txt"> with name="file" value="(.+?)">, where +? stops at the first possible end. It then recommends the more efficient name="file" value="([^"]+)">: a negated class [^"] lets the engine stop at the first quote instead of checking every possible end. The same pattern gives the Bookshelf token: "token"\s*:\s*"([^"]+)".

"Contains" versus "matches". In the Response Assertion, "contains" means the expression matches part of the text, "matches" means it matches the whole text. Extractors search, so they behave like "contains".

Testing. Use the View Results Tree Regexp Tester on a real response, or a small plan with a Debug Sampler: the manual suggests generating a sample with a Java Request or an HTTP Request that loads a file, so expressions can be tested without a server.

Boundary Extractor

The Boundary Extractor (class BoundaryExtractor, added in 4.0) extracts the text between a left boundary and a right boundary, with no regular expression at all. The 4.0 release notes present it as "easy extraction with better performances". It is the cheapest extractor for a value that always sits between the same two literal strings.

Add it with Add → Post Processors → Boundary Extractor.

Field (GUI label) Meaning Required

Name

Descriptive name shown in the tree.

No

Apply to

Main sample only, Sub-samples only, Main sample and sub-samples, or JMeter Variable Name to use. Counting across several samples works as for the Regular Expression Extractor.

Yes

Field to check

Same options as the Regular Expression Extractor: Body, Body (unescaped), Body as a Document, Request Headers, Response Headers, URL, Response Code, Response Message. JMX: BoundaryExtractor.useHeaders, same values.

Yes

Name of created variable

The variable name. JMX: BoundaryExtractor.refname.

Yes

Left Boundary

Literal text immediately before the value. JMX: BoundaryExtractor.lboundary.

No

Right Boundary

Literal text immediately after the value. JMX: BoundaryExtractor.rboundary.

No

Match No. (0 for Random)

0 random, N the N-th match, negative for all matches. JMX: BoundaryExtractor.match_number.

Yes

Default Value

Stored when the boundaries do not match. "No, but recommended". JMX: BoundaryExtractor.default.

No

Use empty default value

Present in the 5.6.3 GUI although the component reference table does not list it: as in the Regular Expression Extractor, sets an empty string instead of leaving the variable unset. JMX: BoundaryExtractor.default_empty_value.

No

Both boundaries may be empty (since 5.2); if both are empty, the whole selected data is returned.

Variables. With a non-negative match number, name = the extracted text, or the default when nothing matches (only if a default is set or the empty-default box is ticked). With a negative match number, name_matchNr = the number of matches (possibly 0) and name_1, name_2…​ the values, while name holds the default. The component reference also lists name_n_gm group variables for this mode, copied from the regex documentation; a boundary extraction has no groups, and the 5.6.3 code (BoundaryExtractor) does not write them. name_matchNr exists only in the negative mode.

<BoundaryExtractor guiclass="BoundaryExtractorGui" testclass="BoundaryExtractor" testname="Token (boundary)">
  <stringProp name="BoundaryExtractor.useHeaders">false</stringProp>
  <stringProp name="BoundaryExtractor.refname">token</stringProp>
  <stringProp name="BoundaryExtractor.lboundary">"token":"</stringProp>
  <stringProp name="BoundaryExtractor.rboundary">"</stringProp>
  <stringProp name="BoundaryExtractor.match_number">1</stringProp>
  <stringProp name="BoundaryExtractor.default">TOKEN_NOT_FOUND</stringProp>
  <boolProp name="BoundaryExtractor.default_empty_value">false</boolProp>
</BoundaryExtractor>
<hashTree/>

The price of speed is fragility: the boundaries are literal, so "token": " (with a space) or a different field order breaks the extraction silently, apart from the default value. Use it when the format is under the team’s control, and the JSON Extractor when it is not.

CSS Selector Extractor

The CSS Selector Extractor (class HtmlExtractor; called CSS/JQuery Extractor until 5.0) selects nodes of an HTML response with a CSS selector and stores an attribute value or the node’s text. It is the right extractor for HTML: the component reference says that for HTML the CSS Selector Extractor is "the correct and performing solution", and that XPath should not be used for HTML.

Add it with Add → Post Processors → CSS Selector Extractor.

Field (GUI label) Meaning Required

Name

Descriptive name shown in the tree.

No

Apply to

Main sample only, Sub-samples only, Main sample and sub-samples, or JMeter Variable Name to use.

Yes

CSS Selector Extractor Implementation

JSoup or Jodd-Lagarto (CSSelly). Empty means the default, JSoup. The two syntaxes differ slightly. JMX: HtmlExtractor.extractor_impl (JSOUP, JODD or empty).

No

Name of created variable

The variable name. JMX: HtmlExtractor.refname.

Yes

CSS Selector expression

The selector. Selectors, combinations and pseudo-selectors are supported, for example E[foo] (element with a foo attribute), .body p (descendant), td:lt(3) (sibling index below 3), p:contains(jsoup) (case-insensitive text). JMX: HtmlExtractor.expr.

Yes

Attribute

The attribute to read from the matched node (JSoup Element#attr(name)). Empty means the combined text of the element and its children (JSoup Element#text()). JMX: HtmlExtractor.attribute.

No

Match No. (0 for Random)

0 random, N the N-th match, negative for all matches. JMX: HtmlExtractor.match_number.

Yes

Default Value

Stored when nothing matches. "No, but recommended". JMX: HtmlExtractor.default.

No

Use empty default value

Sets an empty string instead of leaving the variable unset (since 3.1). JMX: HtmlExtractor.default_empty_value.

No

Variables. With a non-negative match number, name = the value, or the default. With a negative match number, name_matchNr = the number of matches (possibly 0), name_1, name_2…​ the values, and name = the default. There are no group variables, and name_matchNr exists only in the negative mode.

Bookshelf is a JSON API, so the example uses a hypothetical server-rendered admin form with a CSRF token, <input type="hidden" name="_csrf" value="...">:

<HtmlExtractor guiclass="HtmlExtractorGui" testclass="HtmlExtractor" testname="CSRF token">
  <stringProp name="HtmlExtractor.refname">csrf</stringProp>
  <stringProp name="HtmlExtractor.expr">input[name=_csrf]</stringProp>
  <stringProp name="HtmlExtractor.attribute">value</stringProp>
  <stringProp name="HtmlExtractor.default">CSRF_NOT_FOUND</stringProp>
  <stringProp name="HtmlExtractor.match_number">1</stringProp>
  <stringProp name="HtmlExtractor.extractor_impl"></stringProp>
  <boolProp name="HtmlExtractor.default_empty_value">false</boolProp>
</HtmlExtractor>
<hashTree/>

The form POST then sends _csrf with the value ${csrf}. When the server sets the token in a cookie instead, no extractor is needed if the property CookieManager.save.cookies is true: the Cookie Manager then also stores each cookie as a variable named COOKIE_ followed by the cookie name; see HTTP Sessions, Headers, Cookies & Authentication.

XPath2 Extractor

The XPath2 Extractor (class XPath2Extractor, added in 5.0) evaluates an XPath 2.0 query against an XML or XHTML response. It is backed by Saxon. Since 5.0 the component reference recommends it over the older XPath Extractor for "better and easier namespace management, better performances and support for XPath 2.0". Use it for XML APIs and SOAP responses; for HTML use the CSS Selector Extractor.

Add it with Add → Post Processors → XPath2 Extractor.

Field (GUI label) Meaning Required

Name

Descriptive name shown in the tree.

No

Apply to

Main sample only, Sub-samples only, Main sample and sub-samples, or JMeter Variable Name to use. All matching results of all qualifying samples are returned.

Yes

Return entire XPath fragment instead of text content?

When ticked, the matched node is returned as XML (<title>Apache JMeter</title>) rather than its text (Apache JMeter). JMX: XPathExtractor2.fragment.

Yes

Name of created variable

The variable name. JMX: XPathExtractor2.refname.

Yes

XPath query

An XPath 2.0 expression; it may return several nodes. XPath 2.0 functions such as abs(), avg() and compare() are available. JMX: XPathExtractor2.xpathQuery.

Yes

Match No. (0 for Random)

0 random (the default), -1 all results as name_N, N the N-th result; if N is larger than the number of results, nothing is returned and the default is used. JMX: XPathExtractor2.matchNumber.

No

Default Value

Returned when nothing matches, and when the node has no value and the fragment option is off. JMX: XPathExtractor2.default.

Yes

Namespaces aliases list

One prefix=namespace-uri declaration per line, for the prefixes used in the query. JMX: XPathExtractor2.namespaces.

No

Variables. The component reference says the XPath2 Extractor works "exactly the same" as the XPath Extractor for ForEach use, and the 5.6.3 code confirms it: whatever the match number, the extractor first sets name to the default and name_matchNr to 0, then writes name_matchNr = the number of values kept, name = the first value kept, and name_1 …​ name_N. With Match No. -1 that is every result; with N or 0 it is the one selected value, so name_matchNr is 1 and name_1 equals name. This makes XPath2 the only extractor here that sets both name and name_matchNr in every mode.

A hypothetical XML representation of the Bookshelf search, in namespace urn:bookshelf:v1:

<XPath2Extractor guiclass="XPath2ExtractorGui" testclass="XPath2Extractor" testname="Book ids (XML)">
  <stringProp name="XPathExtractor2.default">NO_BOOK</stringProp>
  <stringProp name="XPathExtractor2.refname">bookIdXml</stringProp>
  <stringProp name="XPathExtractor2.matchNumber">-1</stringProp>
  <stringProp name="XPathExtractor2.xpathQuery">/bs:books/bs:book/bs:id</stringProp>
  <boolProp name="XPathExtractor2.fragment">false</boolProp>
  <stringProp name="XPathExtractor2.namespaces">bs=urn:bookshelf:v1</stringProp>
</XPath2Extractor>
<hashTree/>

The older XPath Extractor (class XPathExtractor) still exists, with Tidy options for HTML (Use Tidy, Quiet, Report Errors, Show warnings) and XML parser options (Use Namespaces, Validate XML, Ignore Whitespace, Fetch External DTDs), and namespaces declared through the xpath.namespace.config property. New plans should not use it.

Which extractor sets what: the -1 and _matchNr rules

The extractors look alike in the GUI but do not write the same variables. This table summarises the sections above for the 5.6.3 code; name is the variable name configured in the element.

Extractor name_matchNr written when name with Match No. -1 Other variables

Regular Expression

Only with a negative Match No. (0 if nothing matched). Removed otherwise.

Always the default value.

name_N; groups name_gN, name_g (single match) or name_N_gM (all matches).

Boundary

Only with a negative Match No.

The default value, if one is set.

name_N. No groups.

CSS Selector

Only with a negative Match No.

The default value, if one is set.

name_N. No groups.

XPath2

Always: the number of values kept (1 for a single selected match, 0 for none).

The first match (default if none).

name_N in every mode.

JSON Extractor

With -1 or N > 0 when there are results; 0 when the expression returns nothing; not with Match No. 0 and results.

Not written: keeps its previous value (default if no result).

name_N; name_ALL with the concatenation option.

JSON JMESPath

Whenever there are results (any Match No.); 0 when nothing matches.

Not written: keeps its previous value (default if no result).

name_N.

Two practical rules follow:

  • To loop over all matches, use -1 and read name_matchNr for the count. Every extractor supports this, and the ForEach Controller only needs the numbered variables.

  • Do not read name after a -1 extraction. Depending on the extractor it is the default, the first match, or a stale value from an earlier sample. Read name_1 instead.

Result Status Action Handler

The Result Status Action Handler (class ResultAction) does not extract anything. It decides what the thread does when the sampler in its scope fails, "either because the sample itself failed or an assertion failed". It is the per-sampler version of the Thread Group’s "Action to be taken after a Sampler error", and it belongs on this page because a failed correlation is the most common reason to stop a user: without a token, every later request of the journey only adds 401 errors to the results.

Add it with Add → Post Processors → Result Status Action Handler.

Field (GUI label) Meaning Required

Name

Descriptive name shown in the tree.

No

Action to be taken after a Sampler error

What happens when the sample is marked failed. JMX: OnError.action (integer, values below).

No

Option Effect OnError.action

Continue

Ignore the error and continue. The default.

0

Start Next Thread Loop

Skip the rest of the current Thread Group iteration and start the next one.

4

Go to the next iteration of Current Loop

Skip the rest of the innermost loop (Loop, While, ForEach Controller) and start its next iteration.

5

Break Current Loop

Leave the innermost loop.

6

Stop Thread

The current thread exits.

1

Stop Test

The whole test stops at the end of the current samples.

2

Stop Test Now

The whole test stops abruptly; current samplers are interrupted if possible.

3

The component reference table lists five choices; the two current-loop options were added in 5.0 ("Loop breaking and Loop switching to next iteration") and appear in the 5.6.3 GUI. The handler runs after the assertions of the sample (it reacts to the final status), so an assertion that checks the extracted value can trigger it.

<ResultAction guiclass="ResultActionGui" testclass="ResultAction" testname="Restart iteration if login failed">
  <intProp name="OnError.action">4</intProp>
</ResultAction>
<hashTree/>

Which action under load? Stop Thread lowers the concurrency for the rest of the run, so the test silently stops applying the planned load; Stop Test ends the run on the first failure, which suits a smoke test but not a load test. Start Next Thread Loop keeps the user alive and tries again on the next iteration, which keeps the concurrency constant and is usually the right choice. Whatever the action, failed logins still appear as errors in the results.

Supporting techniques

Default values that make failures visible

Every extractor has a default value, and the component reference recommends setting one: without it, there is no way to tell whether the expression did not match, the extractor did not run, or a different variable name was used. Use a loud, unmistakable default such as TOKEN_NOT_FOUND or LOANID_NOT_FOUND, never a plausible value like 0 or 1.

A visible default turns a silent correlation failure into an obvious one:

  • the next request carries Bearer TOKEN_NOT_FOUND or /api/loans/LOANID_NOT_FOUND, which is easy to spot in the View Results Tree and in the server logs;

  • an assertion can check for it. A Response Assertion on the JMeter Variable token with the pattern TOKEN_NOT_FOUND and Not ticked fails the login sample when extraction failed, and the Result Status Action Handler above then acts on it (see Assertions);

  • an If Controller can skip dependent requests: ${__groovy(vars.get("loanId") != "LOANID_NOT_FOUND")}.

When several extractors deliberately write the same variable (one of several possible responses), the component reference suggests removing the default once debugging is finished, so a non-matching extractor leaves the value from the matching one in place. Use empty default value is for genuinely optional values, where an empty string is a valid answer.

ForEach over all matches

With Match No. -1 every extractor writes name_1 …​ name_N, the input the ForEach Controller expects. The controller and its fields are described in Logic Controllers & Transactions; the Bookshelf plan opens the first three search results:

<ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="Open first results">
  <stringProp name="ForeachController.inputVal">bookId</stringProp>
  <stringProp name="ForeachController.returnVal">currentBookId</stringProp>
  <boolProp name="ForeachController.useSeparator">true</boolProp>
  <stringProp name="ForeachController.startIndex">0</stringProp>
  <stringProp name="ForeachController.endIndex">3</stringProp>
</ForeachController>
<hashTree>
  <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /api/books/{id}">
    <stringProp name="HTTPSampler.path">/api/books/${currentBookId}</stringProp>
    <stringProp name="HTTPSampler.method">GET</stringProp>
  </HTTPSamplerProxy>
  <hashTree/>
</hashTree>

Two alternatives avoid the loop when only one value is needed:

  • Match No. 0 stores a random match directly in name.

  • With -1, a function picks an index: ${__V(bookId_${__Random(1,${bookId_matchNr})})}. __Random returns a number between 1 and the match count (both inclusive) and __V evaluates the composed variable name. Guard it with an If Controller on ${bookId_matchNr} greater than 0, because __Random(1,0) is not a valid range.

To loop over the groups of one regex match (name_g1, name_g2…​), untick Use Separator in the ForEach Controller and use name_g as the prefix.

Debug Sampler and Debug PostProcessor

Two elements show the variables an extractor produced, in the View Results Tree’s Response data pane.

Debug Sampler (Add → Sampler → Debug Sampler) generates a sample containing the current values of the JMeter variables and/or properties. Place it right after the sampler being debugged.

Field Meaning Required

Name

Descriptive name shown in the tree.

No

JMeter Properties

Include JMeter properties.

Yes

JMeter Variables

Include JMeter variables.

Yes

System Properties

Include Java system properties.

Yes

Debug PostProcessor (Add → Post Processors → Debug PostProcessor) adds a sub-sample to the previous sampler’s result with the sampler’s properties, the variables, the JMeter properties and/or the system properties. Being a post-processor, it runs after the extractors placed above it under the same sampler, so it shows their output for that exact sample.

Field Meaning Required

Name

Descriptive name shown in the tree.

No

JMeter Properties

Show JMeter properties (default false).

Yes

JMeter Variables

Show JMeter variables (default false).

Yes

Sampler Properties

Show the sampler’s properties (default true).

Yes

System Properties

Show Java system properties (default false).

Yes

<DebugPostProcessor guiclass="TestBeanGUI" testclass="DebugPostProcessor" testname="Debug PostProcessor">
  <boolProp name="displayJMeterProperties">false</boolProp>
  <boolProp name="displayJMeterVariables">true</boolProp>
  <boolProp name="displaySamplerProperties">false</boolProp>
  <boolProp name="displaySystemProperties">false</boolProp>
</DebugPostProcessor>
<hashTree/>

With JMeter Variables ticked, the sub-sample’s response data lists the thread’s variables in alphabetical order, for example (shortened, values illustrative):

JMeterVariables:
JMeterThread.last_sample_ok=true
loanId=1001
loanIdFromHeader=1001
loanStatus=PENDING
token=eyJhbGciOiJIUzI1NiJ9.<shortened>
tokenTtl=900

Both elements are debugging aids. Disable them before a load run: the Debug Sampler adds samples to the results and both copy every variable into a result for every iteration, which costs memory and CPU and pollutes the statistics.

Worked example: the Bookshelf login token and the new loan id

The Bookshelf journey logs in, searches, views a book, creates a loan and views it. Two values must be correlated:

  1. the JWT from POST /api/auth/login, sent as Authorization: Bearer <token> on every later request;

  2. the loan id from POST /api/loans, used in the path of GET /api/loans/{id}.

The exchanges, with the values the extractors capture, are:

POST /api/auth/login
Content-Type: application/json

{"username":"reader001","password":"<from users.csv>"}

HTTP/1.1 200 OK
Content-Type: application/json

{"token":"eyJhbGciOiJIUzI1NiJ9.<shortened>","tokenType":"Bearer","expiresIn":900}

POST /api/loans
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.<shortened>
Content-Type: application/json

{"bookId":42}

HTTP/1.1 201 Created
Location: /api/loans/1001
Content-Type: application/json

{"id":1001,"bookId":42,"status":"PENDING"}

Step by step in the GUI

  1. Under the login sampler POST /api/auth/login, add Add → Post Processors → JSON Extractor. Set Names of created variables to token, JSON Path expressions to $.token, Match No. to 1 and Default Values to TOKEN_NOT_FOUND.

  2. Under the same sampler, add Add → Assertions → JSON Assertion with Assert JSON Path exists set to $.token, so a login without a token is a failed sample (see Assertions).

  3. Under the same sampler, add Add → Post Processors → Result Status Action Handler and select Start Next Thread Loop.

  4. Add a Simple Controller named "Authenticated requests" (Add → Logic Controller → Simple Controller) after the login, move the search, view and loan requests into it, and add to it Add → Config Element → HTTP Header Manager with the header Authorization and the value Bearer ${token}. The Header Manager is resolved when each sampler runs, so every request in the controller sends the token captured at login, and the login request itself sends no Authorization header (scoping is explained in Test Plan Elements, Execution Order & Scoping).

  5. Under POST /api/loans, add a JSON Extractor with Names of created variables loanId;loanStatus, JSON Path expressions $.id;$.status, Match No. 1;1 and Default Values LOANID_NOT_FOUND;UNKNOWN.

  6. Optionally, under the same sampler, add the Regular Expression Extractor on Response Headers shown earlier, to cross-check the id in the Location header while debugging.

  7. In GET /api/loans/{id}, set Path to /api/loans/${loanId}.

  8. Add a Debug PostProcessor under POST /api/loans and a View Results Tree, run the plan with one thread (Validate on the Thread Group), and check that token, loanId and loanStatus hold real values.

  9. Disable the Debug PostProcessor and the View Results Tree before any load run.

The resulting test plan fragment

<OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Login once per user"/>
<hashTree>
  <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /api/auth/login">
    <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>
    <stringProp name="HTTPSampler.path">/api/auth/login</stringProp>
    <stringProp name="HTTPSampler.method">POST</stringProp>
  </HTTPSamplerProxy>
  <hashTree>
    <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Extract token">
      <stringProp name="JSONPostProcessor.referenceNames">token</stringProp>
      <stringProp name="JSONPostProcessor.jsonPathExprs">$.token</stringProp>
      <stringProp name="JSONPostProcessor.match_numbers">1</stringProp>
      <stringProp name="JSONPostProcessor.defaultValues">TOKEN_NOT_FOUND</stringProp>
    </JSONPostProcessor>
    <hashTree/>
    <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="Token present">
      <stringProp name="JSON_PATH">$.token</stringProp>
      <stringProp name="EXPECTED_VALUE"></stringProp>
      <boolProp name="JSONVALIDATION">false</boolProp>
      <boolProp name="EXPECT_NULL">false</boolProp>
      <boolProp name="INVERT">false</boolProp>
      <boolProp name="ISREGEX">false</boolProp>
    </JSONPathAssertion>
    <hashTree/>
    <ResultAction guiclass="ResultActionGui" testclass="ResultAction" testname="Retry next iteration if login failed">
      <intProp name="OnError.action">4</intProp>
    </ResultAction>
    <hashTree/>
  </hashTree>
</hashTree>
<GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Authenticated requests"/>
<hashTree>
  <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="Bearer token">
    <collectionProp name="HeaderManager.headers">
      <elementProp name="" elementType="Header">
        <stringProp name="Header.name">Authorization</stringProp>
        <stringProp name="Header.value">Bearer ${token}</stringProp>
      </elementProp>
    </collectionProp>
  </HeaderManager>
  <hashTree/>
  <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /api/loans">
    <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">{"bookId":${bookId}}</stringProp>
          <stringProp name="Argument.metadata">=</stringProp>
        </elementProp>
      </collectionProp>
    </elementProp>
    <stringProp name="HTTPSampler.path">/api/loans</stringProp>
    <stringProp name="HTTPSampler.method">POST</stringProp>
  </HTTPSamplerProxy>
  <hashTree>
    <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Extract loan id">
      <stringProp name="JSONPostProcessor.referenceNames">loanId;loanStatus</stringProp>
      <stringProp name="JSONPostProcessor.jsonPathExprs">$.id;$.status</stringProp>
      <stringProp name="JSONPostProcessor.match_numbers">1;1</stringProp>
      <stringProp name="JSONPostProcessor.defaultValues">LOANID_NOT_FOUND;UNKNOWN</stringProp>
    </JSONPostProcessor>
    <hashTree/>
  </hashTree>
  <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /api/loans/{id}">
    <stringProp name="HTTPSampler.path">/api/loans/${loanId}</stringProp>
    <stringProp name="HTTPSampler.method">GET</stringProp>
  </HTTPSamplerProxy>
  <hashTree/>
</hashTree>

The fragment shows only the properties that matter. The Content-Type header (application/json) of the two POST requests, the search and view requests that set bookId, the HTTP Request Defaults with ${__P(host,localhost)} and ${__P(port,8080)}, the CSV Data Set Config for users.csv, timers and Transaction Controllers come from the other pages of this guide. Build the plan in the GUI and read the XML to review it rather than writing whole plans by hand.

Checking the correlation from the command line

A one-user smoke run in CLI mode confirms the correlation before any load is applied. With the canonical run properties of this guide:

jmeter -n -t bookshelf-load.jmx -Jthreads=1 -Jrampup=1 -Jduration=30 -Jhost=localhost -Jport=8080 -l smoke.jtl

The summariser prints lines of this form (numbers illustrative):

summary =     25 in 00:00:30 =    0.8/s Avg:    38 Min:     6 Max:   142 Err:     0 (0.00%)

Err: 0 means no sample failed, so every request ran with a real token and a real loan id. A failed correlation shows up immediately as errors on the requests after the extraction (401 without a token, 404 for /api/loans/LOANID_NOT_FOUND). To find the extraction that failed, search the results for the default values:

grep -c "LOANID_NOT_FOUND\|TOKEN_NOT_FOUND" smoke.jtl

This only finds them if the URL is saved in the JTL (jmeter.save.saveservice.url=true, off by default) or if the default appears in a sample label or failure message; otherwise rerun the single user with the View Results Tree.

Worked numbers: how often the extractors run

Extractors run per sample, per thread, so their cost scales with the request rate, not with the number of extractors in the tree. With the load profile of this guide (50 threads, 60 s ramp-up, 660 s scheduler duration) and the Bookshelf mix (60 % search only, 30 % browse and borrow, 10 % login-heavy), take the mean iteration of about 8.5 s (Z = 3 s of think time per request included) worked out in Logic Controllers & Transactions, so at full load the 50 threads complete 50 / 8.5 ≈ 5.9 iterations per second:

  • Token extraction, login once per user: 50 executions in the whole run, one per thread, plus the logins of the login-heavy iterations: 10 % × 5.9 ≈ 0.6 per second.

  • Loan id extraction: only the browse-and-borrow iterations create loans: 30 % × 5.9 ≈ 1.8 per second.

  • A misplaced extractor: if the token JSON Extractor sat at Thread Group level instead of under the login sampler, it would run after every sample. At, say, 5 samples per iteration that is about 30 executions per second, 50 times more than needed, and each of them would overwrite token with TOKEN_NOT_FOUND, because the other responses have no token field. The second problem is the serious one: the test breaks, not only slows down.

Load-generator cost

Extractors run inside the load generator, in the thread that sends the requests, so a slow extractor delays the next request of that virtual user and lowers the achievable throughput. The official guidance and the implementations give this ranking, from cheapest to most expensive:

Extractor Cost driver

Boundary Extractor

Plain string search. Introduced in 4.0 for "better performances".

Regular Expression Extractor

Regex matching over the selected field. Cheap for anchored expressions and negated classes; expensive for .* patterns and back-tracking on large bodies. Compiled patterns are cached.

JSON Extractor and JSON JMESPath Extractor

Parse the whole body into a tree. In the 5.6.3 JSON Extractor each expression is evaluated against the response text separately, so one element with five expressions parses five times. Cost grows with the body size.

CSS Selector Extractor

Parses the whole HTML document into a DOM.

XPath2 Extractor

Builds a full XML document with Saxon for every sample; the most expensive on large documents. The older XPath Extractor with Tidy is worse still.

Field to check: Body (unescaped), Body as a Document

The manual warns that unescaping "highly impacts performances" and that Body as a Document (Tika) can too.

The rules that keep the cost down:

  • Scope every extractor to the one sampler that needs it.

  • Extract from the smallest field that contains the value: a header rather than the body when the value is in both.

  • Prefer one JSON Extractor with the expressions it needs to several elements, but remember that each expression is evaluated separately.

  • Avoid deep scans ($..id) and greedy regular expressions on large responses.

  • Remove Debug Sampler, Debug PostProcessor and View Results Tree before load runs.

Pitfalls

  • Extractor in the wrong scope. An extractor at Thread Group or controller level runs after every sampler and overwrites the variable, usually with the default. Put it under the sampler.

  • No default value. An unset variable stays as the literal text ${token} in the next request, which is hard to tell apart from a typo in the variable name. Always set a loud default while building the plan.

  • Empty Match No. In the JSON and JMESPath Extractors it means 0, a random pick. With one match the result is the same, so the bug only shows when the response unexpectedly contains several matches.

  • Reading name after -1. See Which extractor sets what: the -1 and _matchNr rules; read name_1 and name_matchNr.

  • Assuming name_matchNr exists. It is not written by the Regular Expression, Boundary and CSS Selector Extractors unless Match No. is negative, and not by the JSON Extractor with Match No. 0 and results. An If Controller on ${name_matchNr} then compares a literal string.

  • JSON Extractor list lengths. Names, expressions and defaults must have the same count, and a trailing empty default does not count in 5.6.3.

  • Regular expressions over JSON or HTML. They break when the server changes whitespace, field order or escaping. Prefer the JSON, JMESPath or CSS Selector Extractor for structured content.

  • Header name case. HTTP/2 and many proxies send lower-case header names; use (?i) in header regexes.

  • Variables are per thread. A token extracted in a setUp Thread Group is not visible in the main Thread Group. Publish it as a property with __setProperty and read it with __P (see Properties, Variables & Functions).

  • Token expiry in long tests. A JWT obtained once per user can expire during a soak test. Log in again on a schedule (with the tokenTtl value extracted above) or make the server’s token lifetime longer than the test.

  • Post-processors and sub-samples. Unless Apply to says otherwise, extractors only see the main sample. Before 5.5 the JSON and JMESPath extractors ignored sub-samples even when asked to read them (fixed in 5.5).

  • Correlation hidden behind redirects. With Follow Redirects, the final response is the main sample and the intermediate responses are sub-samples; a value in an intermediate Location header needs Sub-samples only or Main sample and sub-samples.

From the books to 5.6 to 6.0

Topic In the books (JMeter 2.11 to 3.0) 5.6.3 and planned 6.0

JSON extraction

The jp@gc JSON Path Extractor plugin (Cookbook ch. 2; PTwJ ch. 7).

Core JSON Extractor since 3.0 (renamed from "JSON Path PostProcessor" in 3.1), JSON JMESPath Extractor since 5.2. The jpgc-json plugin is deprecated. 6.0 (planned) updates json-path to 3.0.0 and Jackson, and supports trailing empty default values in the JSON Extractor.

Scripted extraction

BSF and BeanShell post-processors (PTwJ ch. 3; Pro ch. 3).

BSF was deprecated in 3.1 (it still ships hidden in 5.6.3); use the JSR223 PostProcessor with Groovy and script caching (see Scripting with JSR223 & Groovy). 6.0 (planned) moves to Groovy 5.

Boundary extraction

Not available; regular expressions only.

Boundary Extractor since 4.0, with empty boundaries allowed since 5.2.

CSS selectors

CSS/JQuery Extractor.

Renamed CSS Selector Extractor in 5.0. 6.0 (planned) updates jsoup to 1.23.2.

XPath

XPath Extractor (XPath 1.0, Xalan, Tidy).

XPath2 Extractor since 5.0 (Saxon). 6.0 (planned) updates Saxon-HE to 13.x.

Regular expression engine

ORO only.

ORO by default; jmeter.regex.engine switches to the JDK engine since 5.5.

Body as a Document

Tika 1.x.

6.0 (planned) updates Apache Tika to 4.x.

Result Status Action Handler

Continue, Start next thread loop, Stop Thread, Stop Test, Stop Test Now.

Plus Go to the next iteration of Current Loop and Break Current Loop since 5.0.

References