Assertions

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. The JSR223 Assertion examples use the Groovy engine bundled with 5.6.3 (Groovy 5 is planned for 6.0). The books on the market target JMeter 2.11 to 3.0: they assert on JSON through the jp@gc JSON Path plugin, which core JMeter replaced with the JSON Assertion in 4.0, and they predate the JMESPath and XPath2 assertions (both 5.2). Every field name, property and rule on this page is taken from the official component reference, best practices and properties reference; where the documentation is silent (JMX property names, defaults, the extra Size Assertion fields), the 5.6.3 source code is the reference, and that is said explicitly.

A sampler only knows whether the transport worked: an HTTP Request is successful when it gets a response whose status is not 4xx or 5xx, whatever the body says. A Bookshelf search that returns 200 OK with an empty list because the search index is down, or a login that returns 200 with an error page from a proxy, would count as a success and pull the response-time statistics down with fast, wrong answers. Assertions close that gap: they inspect each result and mark the sample failed when it is not what the user would have needed. This page covers what assertions are, which samples they apply to, every core assertion type, how failures show up in results, and what each type costs on the load generator, because in a load test every assertion runs once per sample, thousands of times per minute. The running example is the Bookshelf API journey used throughout the section: log in, search, view a book, create a loan, view the loan.

What an assertion does

An assertion is a test element that receives the SampleResult of a sampler after the sampler and its post-processors have run, performs a check, and returns an assertion result. If the check fails, the sample is marked as failed and the assertion’s failure message is attached to it. Nothing else changes: the response time, bytes and response code are recorded exactly as measured. The failed sample then counts as an error everywhere results are aggregated (the summariser’s Err: column, the success column of the JTL file, the dashboard error rate and Errors tables).

The official definition is short: assertions perform additional checks on samplers and are processed after every sampler in the same scope. Three rules follow from it.

  • Order. Within the processing of one sampler, assertions run after the post-processors and before the listeners. An assertion can therefore check a variable that a JSON Extractor has just set, and the listeners always see the final pass or fail status.

  • Scope. An assertion applies to every sampler in its scope: a child of a sampler checks only that sampler; an assertion at the Thread Group or controller level checks every sampler below it.

  • Parent sample only, by default. Unless documented otherwise, assertions are not applied to sub-samples (embedded resources, the samples generated by a Transaction Controller, the messages of a Mail Reader), only to the parent sample. Scoped assertions offer an Apply to choice to change that.

After all the assertions of a sampler have run, JMeter updates the variable ${JMeterThread.last_sample_ok} to true or false. The Thread Group’s Action to be taken after a Sampler error treats a failed assertion exactly like a failed sample: Continue, Start Next Thread Loop, Stop Thread, Stop Test or Stop Test Now.

flowchart TD S[Sampler runs and produces a SampleResult] --> P[Post-processors in scope, e.g. JSON Extractor] P --> A{Assertions in scope} A -- all pass --> OK[Sample stays successful] A -- one fails --> F[Sample marked failed, failure message attached] OK --> V[JMeterThread.last_sample_ok = true] F --> W[JMeterThread.last_sample_ok = false] W --> T{Thread Group: action after sampler error} T -- Continue --> L T -- Start Next Thread Loop / Stop Thread / Stop Test --> L V --> L[Listeners and the results file receive the sample]

The full ordering of config elements, pre-processors, timers, post-processors, assertions and listeners, and the scoping rules with worked examples, are on Test Plan Elements, Execution Order & Scoping.

Failure versus error

An assertion result carries two flags. A failure means the check ran and the response did not satisfy it ("expected to contain `token`"). An error means the check itself could not run properly, for example a Response Assertion whose regular expression does not compile ("Bad test configuration") or a JMESPath expression that cannot be parsed. Both make the sample unsuccessful. The distinction matters when reading results: a sudden 100 percent error rate on one sampler right after editing an assertion is usually a broken pattern, not a broken server.

Adding an assertion in the GUI

  1. Select the sampler the check belongs to, for example POST /api/auth/login.

  2. Right-click it and choose Add → Assertions → Response Assertion (or any other assertion in that menu). The shortcut Ctrl+3 adds a Response Assertion to the selected element.

  3. Fill in the fields described below and give the assertion a descriptive name, because the name is what appears next to a failure in the View Results Tree and the Assertion Results listener.

  4. Run the plan once with one thread, open the View Results Tree and check the Assertion result view of each sample before moving to load.

Apply to: main sample, sub-samples or a variable

Assertions that support scoping show an Apply to: group. In 5.6.3 these are the Response, Duration, Size and XPath2 assertions (the scope panel is part of their GUIs in the source; the component reference lists it for Response and Size). The JSON, JMESPath, MD5Hex, XML Schema and JSR223 assertions always look at the main sample; a JSR223 script can read sub-samples itself with prev.getSubResults(), which returns an array (empty if there are none).

Option Meaning JMX value of Assertion.scope

Main sample only

Default. Only the parent sample is checked.

parent (or property absent)

Sub-samples only

Only the child samples are checked: embedded resources of an HTTP Request, Mail Reader messages, samples under a parent-mode Transaction Controller.

children

Main sample and sub-samples

Both.

all

JMeter Variable Name to use

The assertion checks the content of the named variable instead of the response; the variable name goes in the text field next to the option.

variable, with the name in Scope.variable

If a sub-sample fails and the main sample was successful, the main sample is set to failed and an assertion result is added to it. With the variable option, the check is assumed to relate to the main sample, and any failure is applied to the main sample only.

The variable option is the cheap way to assert on something an extractor has already pulled out. For Bookshelf, the JSON Extractor of the login request stores the token in token; a Size Assertion with Apply to: JMeter Variable Name to use = token can check that the extracted value is not empty without scanning the body again. Note that the Size Assertion parses the variable as a number when this option is used (5.6.3 source), so for a string length use a Response Assertion on the variable instead.

Response Assertion

The Response Assertion is the general-purpose check: it compares one field of the request or the response with a list of patterns. It has been in JMeter since the beginning, and most load tests need nothing else.

Add → Assertions → Response Assertion (class ResponseAssertion, GUI AssertionGui).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

Apply to

See Apply to: main sample, sub-samples or a variable.

Yes

Assertion.scope, Scope.variable

Field to Test

Which part of the sample to test:

  • Text Response: the response body, without headers (default);

  • Document (text): text extracted from documents (PDF, Office and so on) by Apache Tika;

  • URL Sampled;

  • Response Code, e.g. 200;

  • Response Message, e.g. OK;

  • Response Headers, including any Set-Cookie headers;

  • Request Headers;

  • Request Data: the request body, without headers.

Yes

Assertion.test_field = Assertion.response_data, Assertion.response_data_as_document, Assertion.sample_label, Assertion.response_code, Assertion.response_message, Assertion.response_headers, Assertion.request_headers or Assertion.request_data

Ignore Status

Sets the sample status to successful before the assertion is evaluated, so that a 4xx or 5xx response can still pass if the patterns match. It also clears any earlier assertion failures on the sample, so use it only on the first assertion.

Yes

Assertion.assume_success

Pattern Matching Rules

How each pattern is compared with the field:

  • Contains: the text contains the regular expression;

  • Matches: the whole text matches the regular expression;

  • Equals: the whole text equals the string, case-sensitive;

  • Substring: the text contains the string, case-sensitive (default since 2.11).

Two check boxes modify the rule: Not inverts the result of every pattern, and Or passes the assertion if any one pattern passes (the default is AND: all patterns must pass).

Yes

Assertion.test_type, a bit set: Matches 1, Contains 2, Not 4, Equals 8, Substring 16, Or 32 (e.g. Substring + Not = 20)

Patterns to Test

The list of patterns, one per row. Add, Add from Clipboard and Delete manage the rows. Each pattern is tested separately; with AND, the first failing pattern stops the evaluation. One assertion with three patterns behaves like three assertions with one pattern each.

Yes

Asserion.test_strings (sic), a collectionProp of stringProp

Custom failure message

Replaces the generated failure message. Since 4.0.

No

Assertion.custom_message

The property name Asserion.test_strings (sic) is the spelling JMeter really writes in .jmx files.

Regular expression flavour. Contains and Matches use Perl5-style regular expressions (the Jakarta ORO engine by default). Patterns are written without delimiters (Price: \d+, not /Price: \d+/). By default the pattern is in multi-line mode: . does not match a newline, while ^ and $ match at the start and end of any line; \s does match a newline. Inline flags change that: (?i) ignores case, (?s) lets . match a newline, and (?is) does both. The property jmeter.regex.engine (default oro) switches to the JDK java.util.regex engine when set to any other value; the properties reference says the JMeter team intends to make the JDK engine the default in a later version, but the planned 6.0 change list does not include it. The JDK engine caches compiled patterns (jmeter.regex.patterncache.size, default 1000).

Equals diffs. When an Equals comparison fails, the failure message shows the received text and the expected text with the differing section marked. Three properties tune it: assertion.equals_section_diff_len (characters shown per section, default 100), and assertion.equals_diff_delta_start and assertion.equals_diff_delta_end (the markers, default [[[ and ]]]).

Empty fields. If the field to test is empty, the assertion fails with "Response was null", unless Not is selected, in which case it always passes.

Example: the login response

The Bookshelf login (POST /api/auth/login) must return 200 and a JSON body with a token. Two Response Assertions, both children of the sampler, check it: one on the response code with Equals, one on the body with Contains and a custom message so that failures group cleanly in the dashboard.

<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>
  <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Login: 200">
    <collectionProp name="Asserion.test_strings">
      <stringProp name="49586">200</stringProp>
    </collectionProp>
    <stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
    <boolProp name="Assertion.assume_success">false</boolProp>
    <intProp name="Assertion.test_type">8</intProp>
    <stringProp name="Assertion.custom_message">Login did not return 200</stringProp>
  </ResponseAssertion>
  <hashTree/>
  <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Login: token present">
    <collectionProp name="Asserion.test_strings">
      <stringProp name="1">"token"\s*:\s*"[^"]+"</stringProp>
    </collectionProp>
    <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
    <boolProp name="Assertion.assume_success">false</boolProp>
    <intProp name="Assertion.test_type">2</intProp>
    <stringProp name="Assertion.custom_message">Login response has no token</stringProp>
  </ResponseAssertion>
  <hashTree/>
</hashTree>

The stringProp names inside Asserion.test_strings are arbitrary keys (the GUI uses the hash code of the value); only the value matters. The credentials come from users.csv through the variables username and password, never from literals in the plan.

Example: expecting an error status

The Bookshelf API answers 404 for an unknown book. A negative test that requests GET /api/books/0 should pass when it gets 404. Without help, the sampler already marks the sample failed (4xx), and no assertion can turn it back to successful. Ignore Status does exactly that: it forces the status to successful first, then the patterns decide.

Field Value

Apply to

Main sample only

Field to Test

Response Code

Ignore Status

selected

Pattern Matching Rules

Equals

Patterns to Test

404

Custom failure message

Unknown book did not return 404

Because Ignore Status clears earlier assertion failures on the sample, this assertion must be the first assertion of the sampler.

Example: NOT and OR

  • Must not contain. A search page must never contain the word Exception (a stack trace leaking into the body): Text Response, Substring, Not, pattern Exception. Assertion.test_type = 16 + 4 = 20.

  • Any of several codes. Creating a loan may answer 201 Created or, for an idempotent retry, 200 OK: Response Code, Equals, Or, patterns 201 and 200. Assertion.test_type = 8 + 32 = 40.

  • Case-insensitive text. Contains with the pattern (?i)available matches Available and AVAILABLE.

JSON Assertion

The JSON Assertion parses the response as JSON, evaluates a JSONPath expression (Jayway JsonPath syntax), and checks that the path exists and, optionally, that its value equals an expected value, matches a regular expression or is null. It arrived in core JMeter 4.0, donated from the jmeter-plugins jp@gc JSON Path Assertion that the books use.

Add → Assertions → JSON Assertion (class JSONPathAssertion, GUI JSONPathAssertionGui).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

Assert JSON Path exists

The JSONPath to evaluate, e.g. $.items[0].id. If the path is not found, the assertion fails.

Yes

JSON_PATH

Additionally assert value

Also compare the value found at the path. Unticked, only existence is checked.

No

JSONVALIDATION

Match as regular expression

Treat Expected Value as a regular expression that must match the whole value. Ticked by default in a new element.

No

ISREGEX

Expected Value

The value to compare with, or the regular expression. Without the regular-expression option, it is parsed as a JSON value, so 2 matches the number 2. To match an empty array, use [].

No

EXPECTED_VALUE

Expect null

The value at the path must be null.

No

EXPECT_NULL

Invert assertion (will fail if above conditions met)

Inverts the whole check.

No

INVERT

The evaluation has three steps: parse the body (fail if it is not JSON), find the path (fail if it is absent), then compare the value if requested. If the path returns an array, each element is compared and the assertion passes if any one matches; a dictionary is converted to a string before comparison.

Indefinite paths. A path such as $.items[*].id or $..id is indefinite: it returns a list. Without an expected value, older versions passed even when the list was empty. Since 5.5, the assertion fails if an indefinite path returns an empty list and no expected value is set. With an expected value, the list is searched for a match.

Example: search returns results

The Bookshelf search must return at least one item for a term from search-terms.csv:

<JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="Search: at least one item">
  <stringProp name="JSON_PATH">$.items[0].id</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">true</boolProp>
</JSONPathAssertion>
<hashTree/>

The definite path $.items[0].id exists only if the list has a first element, which is a sharper check than the indefinite $.items[*].id. A second example checks a value: on GET /api/loans/{id}, the path $.status with Additionally assert value ticked, Match as regular expression ticked and the expected value ACTIVE|PENDING accepts either state, because the regular expression must match the whole value.

JSON JMESPath Assertion

The JSON JMESPath Assertion does the same job with a JMESPath expression instead of JSONPath. Since 5.2. It is useful when the plan already extracts with the JMESPath Extractor, or when the check needs JMESPath’s functions (length(), filters and projections).

Add → Assertions → JSON JMESPath Assertion (class JMESPathAssertion, GUI JMESPathAssertionGui).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

Assert JMESPath exists

The JMESPath expression. It cannot be empty: even when expecting a null or empty result, a valid expression is required, or the expression fails to compile and the assertion reports an error.

Yes

JMES_PATH

Additionally assert value

Compare the result with Expected Value.

No

JSONVALIDATION

Match as regular expression

Treat Expected Value as a regular expression (whole-value match).

No

ISREGEX

Expected Value

Exact value, or regular expression if the option above is ticked.

No

EXPECTED_VALUE

Expect null

The result must be null.

No

EXPECT_NULL

Invert assertion (will fail if above conditions met)

Inverts the check.

No

INVERT

In 5.6.3 the compiled JMESPath expressions are cached (the source keeps a JMESPathCache), so the cost per sample is the JSON parse plus the evaluation.

Example: the search returned a full page

<JMESPathAssertion guiclass="JMESPathAssertionGui" testclass="JMESPathAssertion" testname="Search: page size 20">
  <stringProp name="JMES_PATH">length(items)</stringProp>
  <stringProp name="EXPECTED_VALUE">20</stringProp>
  <boolProp name="JSONVALIDATION">true</boolProp>
  <boolProp name="EXPECT_NULL">false</boolProp>
  <boolProp name="INVERT">false</boolProp>
  <boolProp name="ISREGEX">false</boolProp>
</JMESPathAssertion>
<hashTree/>

Use it in the functional smoke profile only: in the load profile, rare search terms legitimately return fewer than 20 items, and an assertion that fails on correct answers pollutes the error rate.

Duration Assertion

The Duration Assertion fails any response that took longer than a given number of milliseconds. It is the per-request service-level check: "every search must answer within 800 ms".

Add → Assertions → Duration Assertion (class DurationAssertion, GUI DurationAssertionGui).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

Apply to

Main sample, sub-samples, both, or a variable. Present in the 5.6.3 GUI (the component reference table omits it).

No

Assertion.scope

Duration in milliseconds

The maximum elapsed time of each response. A value of 0 disables the check (5.6.3 source). Variables and functions are accepted.

Yes

DurationAssertion.duration

The time compared is the sample’s elapsed time, the same value that appears in the elapsed column. For an HTTP Request that downloads embedded resources, the parent sample’s time includes them.

Example: a search SLA that can change per run

<DurationAssertion guiclass="DurationAssertionGui" testclass="DurationAssertion" testname="Search under SLA">
  <stringProp name="DurationAssertion.duration">${__P(search_sla_ms,800)}</stringProp>
</DurationAssertion>
<hashTree/>
jmeter -n -t bookshelf-load.jmx -l results.jtl -Jthreads=50 -Jrampup=60 -Jduration=660 -Jsearch_sla_ms=600

Use it with care in load tests. A Duration Assertion turns slow responses into errors. That is what a pass-or-fail CI gate needs, but it hides a distinction the analysis needs: the dashboard then shows a high error rate where the real problem is latency, and response-time percentiles still include the failed samples. For the load profile, prefer to record everything and judge latency from percentiles and APDEX in the dashboard (see Performance Testing Fundamentals); keep the Duration Assertion for smoke tests and for hard per-request limits that the business really treats as failures.

Size Assertion

The Size Assertion compares a size in bytes with a number. An empty response is treated as 0 bytes, not as an error.

Add → Assertions → Size Assertion (class SizeAssertion, GUI SizeAssertionGui).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

Apply to

Main sample, sub-samples, both, or a JMeter variable (whose value is then parsed as a number).

Yes

Assertion.scope, Scope.variable

Response Size Field to Test

Full Response (default), Response Headers, Response Body, Response Code or Response Message. For code and message, the length of the text is used. This field is in the 5.6.3 GUI; the component reference does not list it.

No

Assertion.test_field = SizeAssertion.response_network_size, SizeAssertion.response_headers, SizeAssertion.response_data, SizeAssertion.response_code or SizeAssertion.response_message

Size in bytes

The number to compare with.

Yes

SizeAssertion.size

Type of Comparison

=, !=, >, <, >= or <=. The component reference names equal, greater, less and not equal; the GUI also offers the two inclusive comparisons.

Yes

SizeAssertion.operator = 1 (=), 2 (!=), 3 (>), 4 (<), 5 (>=), 6 (<=)

The Size Assertion only reads a number the sampler has already computed, so it is one of the cheapest checks there is. It is a good guard against truncated or empty bodies: a book detail page that is normally around 2 KB can be asserted with Response Body > 500.

<SizeAssertion guiclass="SizeAssertionGui" testclass="SizeAssertion" testname="Book body not truncated">
  <stringProp name="Assertion.test_field">SizeAssertion.response_data</stringProp>
  <stringProp name="SizeAssertion.size">500</stringProp>
  <intProp name="SizeAssertion.operator">3</intProp>
</SizeAssertion>
<hashTree/>

XPath2 Assertion

The XPath2 Assertion parses an XML response and passes if an XPath 2.0 expression matches, or, for a boolean expression such as count(//*error)=2, returns true. It was added in 5.2 as a faster replacement for the XPath Assertion, and it uses Saxon. Use it for SOAP and other XML APIs; SOAP itself is sent with the HTTP Request sampler, as described on REST, JSON, GraphQL & SOAP APIs.

Add → Assertions → XPath2 Assertion (class XPath2Assertion, GUI XPath2AssertionGui).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

Apply to

Main sample, sub-samples, both, or a variable.

No

Assertion.scope, Scope.variable

Namespaces aliases list

One prefix=namespace declaration per line, used to resolve prefixes in the expression.

No

XPath.namespaces

XPath2 Assertion

The expression. / (the default) matches any well-formed document.

Yes

XPath.xpath

Invert assertion

Fails if the expression matches or returns true, succeeds otherwise.

No

XPath.negate

<XPath2Assertion guiclass="XPath2AssertionGui" testclass="XPath2Assertion" testname="SOAP: no fault">
  <stringProp name="XPath.xpath">count(//soap:Fault)=0</stringProp>
  <stringProp name="XPath.namespaces">soap=http://schemas.xmlsoap.org/soap/envelope/</stringProp>
  <boolProp name="XPath.negate">false</boolProp>
</XPath2Assertion>
<hashTree/>

XML Schema Assertion

The XML Schema Assertion validates the response against an XML Schema (XSD) file. It always applies to the main sample.

Add → Assertions → XML Schema Assertion (class XMLSchemaAssertion, GUI XMLSchemaAssertionGUI).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

File Name

The XSD file. A relative path is resolved like other JMeter files; in distributed tests the file must exist on every load generator.

Yes

xmlschema_assertion_filename

Schema validation parses and validates the entire document on every sample; it is a contract test, best run in the smoke profile with one thread, not at load.

MD5Hex Assertion

The MD5Hex Assertion computes the MD5 hash of the response body and compares it with an expected value: 32 hex digits, case not significant. It is the cheapest way to check that a large, static response (a cover image, a generated export) is byte-for-byte what it should be.

Add → Assertions → MD5Hex Assertion (class MD5HexAssertion, GUI MD5HexAssertionGUI).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree.

No

testname attribute

MD5 sum

32 hex digits representing the expected MD5 hash. If empty, the assertion fails ("MD5Hex to test against is empty"). An empty response also fails ("Response was null").

Yes

MD5HexAssertion.size (sic: the property is named size in the 5.6.3 source)

The hash to expect can be computed once from a known-good response, for example with md5sum cover-42.jpg on Linux or md5 cover-42.jpg on macOS. MD5 is used here as a checksum, not for security. A related option on the HTTP Request sampler, Save response as MD5 hash?, stores only the 32-character hash instead of the body, which saves memory on large downloads; any assertion that reads the body then sees the hash, not the content.

JSR223 Assertion

When no declarative assertion fits, the JSR223 Assertion runs a script, normally in Groovy, against the previous sample. It is the right tool for cross-field rules ("the loan’s bookId equals the book that was requested") and for checks on sub-samples. Writing efficient Groovy for JMeter is covered on Scripting with JSR223 & Groovy; this section covers what is specific to assertions.

Add → Assertions → JSR223 Assertion (class JSR223Assertion, a TestBean, so the GUI class is TestBeanGUI).

Field Meaning Required JMX property

Name

Descriptive name shown in the tree; available to the script as Label.

No

testname attribute

Language

The JSR223 language. Use groovy.

Yes

scriptLanguage

Parameters

Text passed to the script as Parameters (one string) and args (split on white space). Functions and variables are evaluated here, which is the safe way to pass ${...} values into a cached script.

No

parameters

Script file

A script file to run instead of the inline script; a relative path is resolved against the user.dir system property.

No

filename

Cache compiled script if available

Compile the script once and reuse it, for languages that implement Compilable (Groovy does; BeanShell and JavaScript do not). With it ticked, the script must not contain ${...} references, because only the first value would be compiled in.

No

cacheKey (true or false)

Script

The script.

Yes, unless a script file is given

script

The script receives log, Label, Filename, Parameters, args, ctx, vars, props, SampleResult and prev (the sample being checked), sampler, OUT and AssertionResult. To fail the sample, the script calls AssertionResult.setFailure(true) and AssertionResult.setFailureMessage("...").

Example: the loan belongs to the requested book

The POST /api/loans sampler sends bookId from the variable bookId and must get back 201 with a loan for that book in state ACTIVE:

import groovy.json.JsonSlurper

if (prev.getResponseCode() != '201') {
    AssertionResult.setFailure(true)
    AssertionResult.setFailureMessage("Loan not created: HTTP " + prev.getResponseCode())
    return
}
def loan = new JsonSlurper().parseText(prev.getResponseDataAsString())
def expectedBook = vars.get('bookId')
if (String.valueOf(loan.bookId) != expectedBook || loan.status != 'ACTIVE') {
    AssertionResult.setFailure(true)
    AssertionResult.setFailureMessage("Unexpected loan: bookId=" + loan.bookId + " status=" + loan.status)
}
<JSR223Assertion guiclass="TestBeanGUI" testclass="JSR223Assertion" testname="Loan matches book">
  <stringProp name="scriptLanguage">groovy</stringProp>
  <stringProp name="parameters"></stringProp>
  <stringProp name="filename">scripts/assert-loan.groovy</stringProp>
  <stringProp name="cacheKey">true</stringProp>
  <stringProp name="script"></stringProp>
</JSR223Assertion>
<hashTree/>

The script reads the expected book with vars.get('bookId'), not ${bookId}, so the compiled script can be cached. Keeping it in a file under the project (scripts/assert-loan.groovy) also makes it reviewable and reusable.

Legacy and debugging-only assertions

Core JMeter 5.6.3 has more assertions than the ones above. They exist for older plans and special cases; none of them belongs in a high-load plan.

Assertion Fields Use it for

HTML Assertion

doctype (omit, auto, strict or loose), Format (HTML, XHTML or XML), Errors only, Error threshold, Warning threshold, Filename (report file).

Checking HTML syntax with JTidy. It runs a full HTML parser over the page and can write a report file per error: a validation tool for one-thread functional runs.

XPath Assertion

Use Tidy (tolerant parser) with Quiet, Report Errors and Show warnings; or, without Tidy, Use Namespaces, Validate XML, Ignore Whitespace, Fetch External DTDs; XPath Assertion (expression); Invert assertion. Namespaces through xpath.namespace.config.

Older XPath 1.0 plans. The component reference warns that the non-tolerant parser can be quite slow because it may download DTDs; use the XPath2 Assertion instead.

XML Assertion

Name only.

Checking that the response is well-formed XML, without DTD or schema validation.

Compare Assertion

Compare Content, Compare Time (maximum time difference, at least 0), Comparison Filters (regex substitutions applied before comparing).

Comparing the samples in its scope with each other. The component reference says it must not be used during a load test because it consumes a lot of memory and CPU.

SMIME Assertion

Verify Signature, Message not signed, Signer Certificate (No Check, Check values, Certificate file), Message Position. Needs the BouncyCastle bcmail and bcprov jars in lib/.

Checking signed mail read by the Mail Reader Sampler ("Store the message using MIME (raw)" must be selected).

BeanShell Assertion

Reset bsh.Interpreter before each call, Parameters, Script file, Script.

Nothing new: the component reference recommends migrating to the JSR223 Assertion with Groovy.

How failures appear in results

Each place where results surface shows assertion failures differently.

Where What appears

Summariser (CLI output)

Failed samples count in Err: and its percentage.

JTL file, CSV

success is false. The failureMessage column (saved by default, jmeter.save.saveservice.assertion_results_failure_message=true) holds one message: the first found among the sample’s assertion results. responseCode keeps the real code, e.g. 200.

JTL file, XML

With jmeter.save.saveservice.assertion_results set to first or all (default none), each assertion result is written with its name, failure and error flags and message.

HTML dashboard

Failed samples count in the error rate. In the Errors and Top 5 Errors by sampler tables, a failed sample whose response code is a success code is grouped under its failure message (or "Assertion failed" when there is no message), while HTTP errors are grouped as code/message.

View Results Tree

The Assertion result view of each sample lists every assertion with its name and message. GUI debugging only.

Assertion Results listener

One line per sample with its assertion failures. The component reference says it must not be used during load tests.

${JMeterThread.last_sample_ok}

false after a failed sample; If and While controllers can branch on it.

Because the dashboard groups assertion failures by message, a Custom failure message with a stable text ("Login response has no token") gives one clean row per kind of failure. A generated message that contains response text (Equals diffs, JSON values, loan ids) produces one row per distinct message and scatters the same problem over many rows.

A run with a broken search index looks like this on the console (the numbers are illustrative):

jmeter -n -t bookshelf-load.jmx -l results.jtl -Jthreads=50 -Jrampup=60 -Jduration=660
summary +    468 in 00:00:30 =   15.6/s Avg:    41 Min:     6 Max:   412 Err:   278 (59.40%) Active: 50 Started: 50 Finished: 0

Every search returns 200 in a few milliseconds, so without the JSON Assertion this run would report 0 percent errors and an excellent average. In results.jtl, the failing rows keep responseCode 200 but have success set to false and the JSON Assertion’s message (the path that was not found) in failureMessage; in the dashboard they form one Errors row named after that message.

The cost of assertions on the load generator

An assertion runs on the load generator, in the thread of the sampler it checks, once per sample. Its cost is added to the work every virtual user does between two requests; it does not add to the measured elapsed time of the sample, but it does consume CPU and memory that the generator needs to keep its threads on schedule. The best-practices page lists "Use as few Assertions as possible" among its ways to reduce resource requirements, next to running in CLI mode and using few listeners.

The official documentation does not publish benchmark numbers for each assertion. The table ranks them by what each one has to do per sample, from the documentation and the 5.6.3 source; the two explicit "must not be used during load test" warnings come from the component reference.

Assertion Work per sample CPU Memory Load test verdict

Size Assertion

Compares a number the sampler already has.

Very low

None

Use freely.

Duration Assertion

Compares the elapsed time with a number.

Very low

None

Cheap, but see the caution about hiding latency as errors.

Response Assertion, Response Code / Message / Headers

Matches short strings.

Very low

Low

Use freely; the standard status check.

Response Assertion, Text Response, Substring / Equals

Plain string search over the whole body.

Low, proportional to body size

Low (body held as a string)

Good default for content checks.

Response Assertion, Text Response, Contains / Matches

Regular-expression scan of the whole body; patterns are cached.

Low to medium; a pattern with heavy backtracking (.* followed by more text) on a large body can be expensive

Low

Fine with anchored, simple patterns.

MD5Hex Assertion

Hashes the whole body.

Low, linear in body size

Low

Fine for static resources.

JSON JMESPath Assertion

Parses the whole body into a tree, evaluates a cached expression.

Medium

Medium (parsed tree per sample)

Fine on small JSON; one per sampler.

JSON Assertion

Parses the whole body, evaluates a JSONPath.

Medium

Medium

Fine on small JSON; prefer definite paths.

JSR223 Assertion, Groovy, cached

Runs compiled code; cost is whatever the script does (usually a JSON parse).

Medium

Medium

Fine when cached and short.

XPath2 Assertion

Parses the whole XML document into a tree, evaluates XPath 2.0.

Medium to high

Medium to high (document tree)

Only on XML APIs, one per sampler.

XML Assertion

Parses the whole document for well-formedness.

Medium

Medium

Rarely worth it at load.

XML Schema Assertion

Parses and validates the whole document against an XSD.

High

Medium to high

Smoke and functional runs only.

Response Assertion, Document (text)

Extracts text from a document through Apache Tika before matching.

High

High

Avoid at load.

XPath Assertion (legacy)

XPath 1.0, optionally through JTidy; the non-tolerant parser may download DTDs.

High

High

Replace with XPath2.

JSR223 Assertion without caching, or in BeanShell / JavaScript

Interprets or recompiles the script on each sample.

High

Medium

Do not use at load; tick caching and use Groovy.

BeanShell Assertion

Interpreted BeanShell per thread.

High

Medium

Migrate to JSR223 + Groovy.

HTML Assertion

Runs JTidy, a full HTML parser, on each page and can write a report file.

High

High

Not for load tests.

Compare Assertion

Keeps samples in memory to compare them with each other.

High

Very high

Must not be used during load tests (component reference).

Worked numbers

The Bookshelf load profile runs 50 threads, a 60 s ramp-up and a scheduler duration of 660 s (a 10-minute hold). With the section’s 3 s average think time (Z = 3 s) and a typical 0.2 s response, each thread issues one request about every 3.2 s, so at full load the plan produces about 50 / 3.2 ≈ 15.6 samples/s, or about 15.6 × 600 ≈ 9,400 samples during the hold.

  • Two Response Assertions per sampler (code and a substring) mean about 31 checks/s. On a 4 KB search body, the substring scan reads about 15.6 × 4 KB ≈ 62 KB/s: negligible on any machine.

  • A JSON Assertion on every sampler means about 16 JSON parses per second, each producing a short-lived object tree. Still modest at this rate, but it scales with the body: if search pages were 200 KB, that would be about 3 MB/s of JSON parsed and as much garbage for the collector.

  • Moving the same plan to an arrival-rate profile of 500 samples/s multiplies everything by about 32. Parsing the whole body several times per sample (a JSON Assertion, a JMESPath Assertion and a JSR223 Assertion that parses it again) is then a real share of the generator’s CPU, and it competes with the threads that should be sending requests on time. That is when the "assert just enough" rule below stops being a style choice.

Watch the load generator’s own CPU and heap during the run. If CPU stays above about 80 percent or garbage collection pauses grow, the generator is distorting the measurement, whatever the assertions report.

Assert just enough

The goal of assertions in a load test is not to prove the application correct (the functional tests did that) but to make sure every sample counted as a success really was a usable answer. That leads to a short policy:

  1. Check the status on every request. A Response Assertion on Response Code at the Thread Group level, or a scoped one per controller, costs almost nothing and catches redirects to login pages and error responses that the sampler treats as success (2xx and 3xx).

  2. Check one content marker per request. One substring or one definite JSON path that only a correct response contains: "token" for login, $.items[0].id for search, "status":"ACTIVE" for a loan. Not the whole document.

  3. Prefer the cheapest assertion that answers the question. Size over content when "not empty or truncated" is the question; Substring over Contains when no regular expression is needed; Response Code over body when the API’s codes are reliable.

  4. Do not parse twice. If a JSON Extractor already pulls a value out, assert on the extracted variable (Response Assertion with Apply to: JMeter Variable Name to use) instead of parsing the body again. Extractors are covered on Correlation & Extractors.

  5. Keep deep checks in the smoke profile. Schema validation, page-size checks, Equals on whole bodies and JSR223 rules run once per sampler with one thread, before the load run, in the same .jmx file if needed (enable them with an If Controller on a property, or keep them in a separate Thread Group that only runs in the smoke profile).

  6. Use stable custom failure messages, so that the dashboard groups failures into a handful of meaningful rows.

  7. Disable debugging assertions and listeners (Compare Assertion, HTML Assertion, Assertion Results, View Results Tree) before running at load.

The Bookshelf assertion set

Sampler Load profile Smoke profile adds

POST /api/auth/login

Response Code Equals 200; Contains "token"\s*:\s*"[^"]+"

Duration ${__P(login_sla_ms,1000)}

GET /api/books?query=

Response Code Equals 200; JSON Assertion $.items[0].id exists

JMESPath length(items) = 20 for common terms

GET /api/books/{id}

Response Code Equals 200; Size Assertion body > 500

JSR223 check of the required fields

POST /api/loans

Response Code Equals 201 (or 201 Or 200); Substring "status":"ACTIVE"

JSR223 "loan matches book"

GET /api/loans/{id}

Response Code Equals 200

JSON Assertion $.status matches ACTIVE|PENDING

The common status check can be a single Response Assertion placed directly under the Thread Group, with each sampler carrying only its content marker. That gives two cheap checks per sample in the load profile, which is the about 31 checks/s of the worked numbers above.

Scoping an assertion to one sampler

The most common assertion bug is scope: an assertion placed at the wrong level of the tree checks samplers it was never meant for.

Thread Group
├── Response Assertion "200 OK"          <- applies to every sampler in the Thread Group
├── POST /api/auth/login
│   └── Response Assertion "token"       <- applies to the login sampler only
├── Simple Controller "Catalogue"
│   ├── JSON Assertion "items present"   <- applies to BOTH samplers below
│   ├── GET /api/books?query=
│   └── GET /api/books/{id}              <- fails: a book detail has no $.items
└── POST /api/loans

The JSON Assertion under the Catalogue controller applies to both catalogue requests, and the book detail request fails because its body has no items. The fix is to move the assertion under GET /api/books?query= as a child. To apply an assertion to exactly one sampler, make it a child of that sampler. Controllers are useful for scoping a check to a group of samplers on purpose, as on Logic Controllers & Transactions, where the Catalogue controller carries a shared 200 OK check.

Transaction Controllers. An assertion placed under a Transaction Controller in its default (non-parent) mode applies to the samplers inside it; the transaction sample itself is generated after them and fails if any child failed. In Generate parent sample mode, the children become sub-samples, and only scoped assertions with Sub-samples only or Main sample and sub-samples see them.

Embedded resources. For an HTTP Request that downloads embedded resources, a Response Assertion with Main sample only checks only the main page. A Main sample and sub-samples status check turns any missing image into a failed page, which is usually not what a backend load test wants.

Pitfalls

  • Regular expression in a Substring rule, or the reverse. Substring and Equals take plain text; \d+ there is looked for literally. Contains and Matches take regular expressions; a . or ? in a plain-text pattern then means something else.

  • Matches instead of Contains. Matches must match the whole field. A pattern that works with Contains usually fails with Matches on a full JSON body.

  • Ignore Status on a later assertion. It wipes the failures recorded by the assertions before it. Put it first.

  • Regex default on in the JSON assertions. Match as regular expression is ticked by default; an expected value such as 1.5 or a+b is then a pattern, not a literal. Untick it for literal comparisons.

  • Indefinite JSONPath without an expected value. It only proves the list exists (and, since 5.5, is not empty), not that it contains what you expect.

  • ${...} inside a cached JSR223 script. Only the first value is compiled in; every later sample is checked against it. Use vars.get() or Parameters.

  • Assertions in the wrong place. An assertion at the Thread Group level checks every sampler, including setup requests and JSR223 samplers that have no HTTP code. See the scoping section above.

  • Asserting on large bodies at high rates. Parsing or scanning megabytes per sample makes the generator the bottleneck; see the cost table.

  • Duration Assertions in the load profile. They mix latency and errors; analyse latency from percentiles instead.

  • Failure messages with variable text. They scatter one problem over many dashboard rows.

From the books to 5.6 to 6.0

Topic Books (JMeter 2.11 to 3.0) 5.6.3 and planned 6.0

JSON checks

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

JSON Assertion in core since 4.0; JSON JMESPath Assertion since 5.2; indefinite empty paths fail since 5.5. Planned for 6.0: json-path 3.0.0 under the JSON Assertion.

XML checks

XPath Assertion and XML Schema Assertion.

XPath2 Assertion since 5.2, faster than XPath. Planned for 6.0: Saxon-HE 13 (from 11).

Response Assertion

Contains, Matches, Equals, Substring, Not; Substring default since 2.11.

Or combination, Request Headers (3.2); Request Data and Custom failure message (4.0); Document (text) through Tika. Planned for 6.0: Apache Tika 4.x (from 1.x) behind Document (text).

Regular expressions

Jakarta ORO only.

jmeter.regex.engine can switch to the JDK engine (5.5); the default is still oro, and the planned 6.0 changes do not switch it.

Scripted assertions

BeanShell and BSF assertions.

BSF deprecated in 3.1 (still shipped in 5.6.3, hidden from the menus and absent from the component reference); BeanShell discouraged; JSR223 + Groovy with compilation caching. Planned for 6.0: Groovy 5 and Java 17 or later.

SMIME Assertion

BouncyCastle jars added by hand.

Still not bundled; 6.0 is planned to build and test against Bouncy Castle 1.86.

Reporting failures

Assertion Results and View Results Tree listeners.

CSV failureMessage, the HTML dashboard’s Errors tables grouped by message; GUI listeners for debugging only.

References