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.
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
-
Select the sampler the check belongs to, for example POST /api/auth/login.
-
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.
-
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.
-
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. |
|
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. |
|
Main sample and sub-samples |
Both. |
|
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. |
|
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.
Source: Assertions (Component Reference)
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 |
|
Apply to |
Yes |
|
|
Field to Test |
Which part of the sample to test:
|
Yes |
|
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 |
|
Pattern Matching Rules |
How each pattern is compared with the field:
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 |
|
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 |
|
Custom failure message |
Replaces the generated failure message. Since 4.0. |
No |
|
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 |
|
Custom failure message |
|
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, patternException.Assertion.test_type= 16 + 4 = 20. -
Any of several codes. Creating a loan may answer
201 Createdor, for an idempotent retry,200 OK: Response Code, Equals, Or, patterns201and200.Assertion.test_type= 8 + 32 = 40. -
Case-insensitive text. Contains with the pattern
(?i)availablematchesAvailableandAVAILABLE.
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 |
|
Assert JSON Path exists |
The JSONPath to evaluate, e.g. |
Yes |
|
Additionally assert value |
Also compare the value found at the path. Unticked, only existence is checked. |
No |
|
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 |
|
Expected Value |
The value to compare with, or the regular expression. Without the regular-expression option, it is parsed as a JSON value, so |
No |
|
Expect null |
The value at the path must be |
No |
|
Invert assertion (will fail if above conditions met) |
Inverts the whole check. |
No |
|
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 |
|
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 |
|
Additionally assert value |
Compare the result with Expected Value. |
No |
|
Match as regular expression |
Treat Expected Value as a regular expression (whole-value match). |
No |
|
Expected Value |
Exact value, or regular expression if the option above is ticked. |
No |
|
Expect null |
The result must be |
No |
|
Invert assertion (will fail if above conditions met) |
Inverts the check. |
No |
|
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 |
|
Apply to |
Main sample, sub-samples, both, or a variable. Present in the 5.6.3 GUI (the component reference table omits it). |
No |
|
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 |
|
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 |
|
Apply to |
Main sample, sub-samples, both, or a JMeter variable (whose value is then parsed as a number). |
Yes |
|
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 |
|
Size in bytes |
The number to compare with. |
Yes |
|
Type of Comparison |
|
Yes |
|
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 |
|
Apply to |
Main sample, sub-samples, both, or a variable. |
No |
|
Namespaces aliases list |
One |
No |
|
XPath2 Assertion |
The expression. |
Yes |
|
Invert assertion |
Fails if the expression matches or returns true, succeeds otherwise. |
No |
|
<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 |
|
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 |
|
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 |
|
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 |
|
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 |
No |
|
Language |
The JSR223 language. Use |
Yes |
|
Parameters |
Text passed to the script as |
No |
|
Script file |
A script file to run instead of the inline script; a relative path is resolved against the |
No |
|
Cache compiled script if available |
Compile the script once and reuse it, for languages that implement |
No |
|
Script |
The script. |
Yes, unless a script file is given |
|
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 |
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 |
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. |
Source: Assertions (Component Reference)
How failures appear in results
Each place where results surface shows assertion failures differently.
| Where | What appears |
|---|---|
Summariser (CLI output) |
Failed samples count in |
JTL file, CSV |
|
JTL file, XML |
With |
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 |
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. |
|
|
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 ( |
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). |
Source: Best Practices: Reducing resource requirements (User Manual) and Assertions (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:
-
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).
-
Check one content marker per request. One substring or one definite JSON path that only a correct response contains:
"token"for login,$.items[0].idfor search,"status":"ACTIVE"for a loan. Not the whole document. -
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.
-
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.
-
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
.jmxfile 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). -
Use stable custom failure messages, so that the dashboard groups failures into a handful of meaningful rows.
-
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 |
|---|---|---|
|
Response Code Equals |
Duration |
|
Response Code Equals |
JMESPath |
|
Response Code Equals |
JSR223 check of the required fields |
|
Response Code Equals |
JSR223 "loan matches book" |
|
Response Code Equals |
JSON Assertion |
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.5ora+bis 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. Usevars.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 |
|
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. |
|
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 |
Source: Changes history, Changes and changes.xml on master
References
-
Assertions (Component Reference), including Response Assertion, JSON Assertion, JSON JMESPath Assertion, Duration Assertion, Size Assertion, XPath2 Assertion, XML Schema Assertion, MD5Hex Assertion, JSR223 Assertion, HTML Assertion and Compare Assertion
-
Best Practices (User Manual), sections "Reducing resource requirements" and "JSR223 Elements"
-
Properties Reference (
jmeter.regex.engine,assertion.equals_*,xpath.namespace.config,jmeter.save.saveservice.assertion_results*) -
Getting Started (User Manual) and Hints and Tips (User Manual)
-
Apache JMeter source (5.6.3 assertion classes,
bin/saveservice.properties,messages.properties, andxdocs/changes.xmlon master for planned 6.0 changes) -
Bayo Erinle, JMeter Cookbook, Packt, 2014 (JMeter 2.11), ch. 2, and Bayo Erinle, Performance Testing with JMeter, 2nd ed., Packt, 2015 (JMeter 2.12), ch. 7: the
jp@gcJSON Path plugin era, used for the historical comparison only.