Troubleshooting & Debugging

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.

A test plan fails in two ways: it does something other than what you meant (the wrong request, an empty variable, a correlation that never matched), or the run itself breaks (the JVM runs out of memory, the generator runs out of ports, a remote engine refuses the connection, a plugin is missing). This page covers both. The first half is the toolbox for debugging a plan: Validate, the Debug Sampler and Debug PostProcessor, the View Results Tree renderers and testers, jmeter.log, per-package log levels and HTTP wire logging. The second half is a catalogue of the failures that come up again and again, each as symptom, cause, fix, with the exact message where it was reproduced and a link to the page of this section that covers the fix in depth.

The page was written against Apache JMeter 5.6.3, the latest release at the time of writing, run on Java 21 (5.6.x needs Java 8 or later; this guide recommends 17 or later). JMeter 6.0 is not released; where its planned changes (from xdocs/changes.xml on the master branch) affect troubleshooting, the text says "planned". The plugin-related entries assume the JMeter Plugins Manager 2.0 and the gRPC Request plugin 2.0.2 (jmeter-grpc-request), and Maven builds the jmeter-maven-plugin 3.8.x. The error messages quoted below were reproduced on a JMeter 5.6.3 installation with Java 21 on macOS, against local endpoints only, or read from the 5.6.3 and plugin sources; where a message depends on the operating system or on the server, the text describes it instead of quoting it.

The method follows the troubleshooting chapter of Pro Apache JMeter (ch. 8, JMeter 3.0): read the log first, make the failure reproducible with one thread, then change one thing at a time. Its tools have changed since then: logging moved from jmeter.properties to Log4j 2 in 3.2, distributed testing uses RMI over SSL by default since 4.0, and plugins are installed with the Plugins Manager. The differences are listed at the end of the page.

Where a failure shows up

The first question is not "what is wrong" but "where does it show": the place narrows the cause down more than the message does.

flowchart TD A["Something is wrong"] --> B{"Does the plan load?"} B -- "No: error in NonGUIDriver or a GUI dialog" --> B1["Missing plugin class, broken .jmx, wrong path: see plugin rows"] B -- yes --> C{"Do samples appear in the results?"} C -- "No: summary shows 0 samples" --> C1["Thread died at start: read jmeter.log for Test failed, e.g. missing CSV file"] C -- yes --> D{"Which column tells the story?"} D -- "responseCode starts with Non HTTP response code" --> D1["Network, TLS, URL or generator problem: Non HTTP rows"] D -- "HTTP 4xx or 5xx" --> D2["Data, correlation or server problem: extractor and CSV rows"] D -- "200 but wrong content" --> D3["Assertions, extractor defaults, unresolved variables"] C -- "Yes, but the run slows down or dies" --> E["Generator limits: heap, ports, CPU: OutOfMemoryError and BindException rows"]

Three rules apply everywhere:

  • Read jmeter.log before anything else. JMeter does not interrupt a test with dialogs. The Getting Started guide says it writes errors to the log file instead, and that it reports no error for a misspelt variable or function: the reference is used as is.

  • Reproduce with one thread. A failure that happens with one thread is a plan or data problem; one that appears only under load is a capacity or generator problem. Use Validate in the GUI or a one-thread CLI run (below).

  • Change one thing at a time, and keep the failing JTL and log of each attempt next to each other.

Debugging a plan

Validate, and the one-thread CLI run

Validate (right-click a Thread Group, Validate) runs that Thread Group with one thread, one iteration, no timers and no Backend Listener, so a journey that takes minutes under its real settings runs in seconds. The properties behind it (testplan_validation.*) and a worked timing example are on GUI & Test Plan Workflow.

Validate exists only in the GUI. When the problem appears in CLI runs (a CI agent, a container, a distributed node), reproduce it in CLI mode with the run properties the Bookshelf plan already reads (threads, rampup, duration), a log of its own and DEBUG for the package under suspicion:

jmeter -n -t bookshelf-load.jmx -l smoke.jtl -j smoke.log -f \
  -Jthreads=1 -Jrampup=1 -Jduration=30 \
  -Lorg.apache.jmeter.protocol.http=DEBUG
Creating summariser <summary>
Created the tree successfully using bookshelf-load.jmx
Starting standalone test @ 2026 Oct 14 10:00:00 CEST (1791964800000)
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary =     10 in 00:00:30 =    0.3/s Avg:   196 Min:   142 Max:   268 Err:     0 (0.00%)
Tidying up ...    @ 2026 Oct 14 10:00:31 CEST (1791964831000)
... end of run

The console lines are those of a 5.6.3 CLI run; the summary figures are illustrative. With the Bookshelf 3 s think time and about 0.2 s per request, one thread makes roughly 1 / 3.2 ≈ 0.31 requests per second, so 30 s give about 10 requests: few enough to read every line of the log, enough to cover the journey twice. Never use these logging settings in a measured run: DEBUG writes several lines per sample and the disk becomes part of the measurement.

Debug Sampler and Debug PostProcessor

Both elements make variables and properties visible in the Response data pane of the View Results Tree. Where to place them and how they help with correlation is on Correlation & Extractors; this section lists their fields with the defaults a new element gets in the 5.6.3 GUI.

Debug Sampler (Add → Sampler → Debug Sampler) produces a sample of its own whose response data lists the current values. It is a sampler, so it appears in the results and counts in the summariser: disable it before a load run.

Field JMX property Default Meaning

Name

testname attribute

Debug Sampler

Label of the sample.

JMeter Properties

displayJMeterProperties

false

Include all JMeter properties (several hundred lines).

JMeter Variables

displayJMeterVariables

true

Include the thread’s variables: what the extractors and CSV files set.

System Properties

displaySystemProperties

false

Include the Java system properties: JVM version, file.encoding, proxy settings.

Debug PostProcessor (Add → Post Processors → Debug PostProcessor) adds a sub-sample to the sampler it is attached to, so the values appear under the request that set them instead of as a separate sample.

Field JMX property Default Meaning

Name

testname attribute

Debug PostProcessor

Name of the element.

JMeter Properties

displayJMeterProperties

false

Include JMeter properties.

JMeter Variables

displayJMeterVariables

true in the 5.6.3 GUI (the component reference says false)

Include JMeter variables.

Sampler Properties

displaySamplerProperties

true

Include the properties of the parent sampler as JMeter evaluated them: the resolved path, the arguments, the body.

System Properties

displaySystemProperties

false

Include Java system properties.

Sampler Properties is the field that answers "what did JMeter actually send": a ${bookId} that was never set appears there literally. As a fragment, attached to the Bookshelf login request:

<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>
  <DebugPostProcessor guiclass="TestBeanGUI" testclass="DebugPostProcessor" testname="Debug login" enabled="true">
    <boolProp name="displayJMeterProperties">false</boolProp>
    <boolProp name="displayJMeterVariables">true</boolProp>
    <boolProp name="displaySamplerProperties">true</boolProp>
    <boolProp name="displaySystemProperties">false</boolProp>
  </DebugPostProcessor>
  <hashTree/>
</hashTree>

The variables listing includes password and, after the login, token. Treat a View Results Tree or an XML results file that contains Debug output as a secret, and remove the element (or set enabled="false") before the plan is committed.

View Results Tree: renderers and testers

The View Results Tree is the debugging listener; its tabs, every renderer and its properties are described on Listeners & Results Files. For troubleshooting, the renderer to pick depends on the question:

Question Renderer or tester How

Did the extractor’s JSONPath match?

JSON Path Tester

Select the response, paste the expression of the JSON Extractor (for Bookshelf login, $.token), click Test. No result means the extractor will write its default value.

Does the JMESPath expression match?

JSON JMESPath Tester

Same, with the expression of the JSON JMESPath Extractor.

Which group does the regular expression capture?

Regexp Tester

Shows Match[n][g] for each match n and group g, with the same engine as the Regular Expression Extractor. [1][1] is what template $1$ with match number 1 returns.

Do the left and right boundaries work?

Boundary Extractor Tester

Tests the boundaries the way the Boundary Extractor applies them.

Does the CSS selector find the element?

CSS/JQuery Tester

Choose the JSoup or Jodd implementation to match the extractor; the syntax differs slightly.

Does the XPath query match a SOAP or XML response?

XPath Tester, XPath2 Tester

XPath Tester follows the XPath Extractor; XPath2 Tester (listed in the 5.6.3 view.results.tree.renderers_order) follows the XPath2 Extractor.

Is the response JSON or XML at all, and what does it contain?

JSON, XML

Tree views; a parse failure here explains an extractor that never matches.

Is the body empty or binary?

Text

Content types starting with image/, audio/ or video/ are treated as binary and not shown. A response without a content type is not shown in any renderer, but post-processors still see it.

Two limits mislead people when they debug with the tree. It keeps only the last 500 samples (view.results.tree.max_results), so the first iterations of a longer debug run disappear; and it does not display responses above view.results.tree.max_size (10485760 bytes in the 5.6.3 jmeter.properties; the component reference still mentions 200 KB). For a bigger body, use Save Responses to a file (below). The Request tab shows only the headers JMeter added, not the ones the HTTP client adds later, such as Host; wire logging shows those. The Browser renderer is absent on Java 9 and later unless JavaFX is added to the launcher, as the Hints and Tips chapter describes.

Saving failed responses in CLI mode

In a CLI run there is no View Results Tree to look at, and the CSV results file has no bodies. Add → Listener → Save Responses to a file with Save Failed Responses only writes one file per failed sample: the body the server actually sent, which is usually the fastest way to the cause of a 500 or a failed assertion. The options to put error bodies into an XML results file instead are on Listeners & Results Files.

Field JMX property Use when troubleshooting

Filename Prefix (can include folders)

FileSaver.filename

~/results/failed/resp_ puts the files next to the plan (~/ is relative to the .jmx). No variables or ${__threadNum} in this field.

Variable Name containing saved file name

FileSaver.variablename

Optional; the file name can then be saved with the sample through sample_variables.

Minimum Length of sequence number

FileSaver.numberPadLen

0 (no padding).

Save Failed Responses only

FileSaver.errorsonly

Tick it. Otherwise every response is written.

Save Successful Responses only

FileSaver.successonly

Off.

Don’t add number to prefix

FileSaver.skipautonumber

Off, or every failure overwrites the same file.

Don’t add content type suffix

FileSaver.skipsuffix

Off: the suffix (.json, .html) tells what came back, for example an HTML error page from a proxy instead of JSON from the API.

Add timestamp

FileSaver.addTimstamp

On, to line the file up with server logs (format yyyyMMdd-HHmm_). The property name is spelled this way in 5.6.3.

Don’t Save Transaction Controller SampleResult

FileSaver.ignoreTC

On, so transaction parents are not saved twice.

<ResultSaver guiclass="ResultSaverGui" testclass="ResultSaver" testname="Failed responses" enabled="true">
  <stringProp name="FileSaver.filename">~/results/failed/resp_</stringProp>
  <stringProp name="FileSaver.variablename"></stringProp>
  <stringProp name="FileSaver.numberPadLen">0</stringProp>
  <boolProp name="FileSaver.errorsonly">true</boolProp>
  <boolProp name="FileSaver.successonly">false</boolProp>
  <boolProp name="FileSaver.skipautonumber">false</boolProp>
  <boolProp name="FileSaver.skipsuffix">false</boolProp>
  <boolProp name="FileSaver.addTimstamp">true</boolProp>
  <boolProp name="FileSaver.ignoreTC">true</boolProp>
</ResultSaver>
<hashTree/>

It costs nothing while samples succeed and one file write per failure. In a run where everything fails (the server is down) it writes one file per sample, so keep it for smoke and debug runs or watch the disk.

jmeter.log and log levels

Since 3.2 JMeter logs through SLF4J and Log4j 2, configured by log4j2.xml (in bin/, or the directory JMeter is started from). The log file is jmeter.log in the directory JMeter was launched from, unless -j names another one. Installation details (the configuration file, the GUI log viewer) are on Installation & Configuration and the command-line options on Running Tests in CLI Mode. The tools at a glance:

Tool What it does

-j file.log

Writes the run log to another file, one per run.

-L with no category, for example -LDEBUG

Sets the root level. Very noisy; rarely what you want.

-L<category>=<level>, for example -Lorg.apache.jmeter.protocol.http=DEBUG

Sets one package’s level for this run. A category starting with jmeter or jorphan gets org.apache. prepended, so -Ljmeter.engine=DEBUG also works; the manual recommends full names.

-i my-log4j2.xml

Uses another Log4j 2 configuration file, for example one kept in the test project.

Options → Log Viewer

Shows the log in a pane at the bottom of the GUI. jmeter.loggerpanel.display=true opens it at start-up; jmeter.loggerpanel.maxlength (default 1000 in jmeter.properties) limits what it keeps.

Options → Log Level

Changes the root level of the running GUI without a restart.

Help → Enable debug / Disable debug

Switches DEBUG on for the class of the selected element only. Help → What’s this node? shows the GUI and test element class names, to know which category to pass to -L.

Error counter

The warning icon at the top right of the GUI counts errors and fatal messages; clicking it opens the log viewer.

The categories worth knowing, all commented examples in the 5.6.3 log4j2.xml:

Category Shows

org.apache.jmeter.protocol.http

How the HTTP sampler builds and sends requests, redirects, embedded resources.

org.apache.jmeter.protocol.http.control

Cookie Manager, Authorization Manager, Header Manager and Cache Manager decisions: why a cookie was not sent.

org.apache.jmeter.protocol.jdbc

JDBC connection pool and statement handling.

org.apache.jmeter.engine, org.apache.jmeter.threads

Engine start, thread start and stop, remote engines.

org.apache.jmeter.control

Logic controller decisions.

org.apache.jmeter.report

Dashboard generation.

org.apache.jmeter.protocol.http.proxy.HttpRequestHdr

Requests seen by the HTTP(S) Test Script Recorder.

org.apache.http, org.apache.http.headers, org.apache.http.wire

HttpClient 4 internals, headers and raw bytes: see the next section.

A log line has the layout date level category: message, with the category shortened (o.a.j.t.JMeterThread). An exception that stops a thread is logged at ERROR by o.a.j.t.JMeterThread with its stack trace, whose at lines name the element class that failed (for example CSVDataSet). Groovy scripts write to the same file through their log binding, which is often the quickest way to print an intermediate value (see Scripting with JSR223 & Groovy).

HTTP wire logging

When the question is what went over the network (which headers, which body, which status line), the View Results Tree’s Request tab is not enough, because it shows only what JMeter added. The default HTTP implementation, Apache HttpClient 4, logs the real exchange through two categories:

  • org.apache.http.headers: the request and response headers, one line each;

  • org.apache.http.wire: every byte, with [\r][\n] for line ends and [0xNN] for non-ASCII bytes. Bodies included.

The shipped log4j2.xml has commented examples for "header wire + context logging" (org.apache.http at DEBUG with org.apache.http.wire at ERROR) and "full wire + context logging" (org.apache.http at DEBUG). For one run, pass the categories with -L:

jmeter -n -t bookshelf-load.jmx -l wire.jtl -j wire.log -f \
  -Jthreads=1 -Jrampup=1 -Jduration=10 \
  -Lorg.apache.http.headers=DEBUG -Lorg.apache.http.wire=DEBUG
grep ' o.a.h.' wire.log | head

The output has this shape (the line format of a 5.6.3 run against a local server; abridged, with the Bookshelf search as the request):

2026-10-14 10:00:47,272 DEBUG o.a.h.headers: http-outgoing-0 >> GET /api/books?query=tolkien HTTP/1.1
2026-10-14 10:00:47,272 DEBUG o.a.h.headers: http-outgoing-0 >> Host: localhost:8080
2026-10-14 10:00:47,272 DEBUG o.a.h.headers: http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.14 (Java/21.0.6)
2026-10-14 10:00:47,272 DEBUG o.a.h.wire: http-outgoing-0 >> "GET /api/books?query=tolkien HTTP/1.1[\r][\n]"
2026-10-14 10:00:47,273 DEBUG o.a.h.wire: http-outgoing-0 >> "[\r][\n]"
2026-10-14 10:00:47,281 DEBUG o.a.h.wire: http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
2026-10-14 10:00:47,281 DEBUG o.a.h.wire: http-outgoing-0 << "Content-Type: application/json[\r][\n]"
2026-10-14 10:00:47,283 DEBUG o.a.h.headers: http-outgoing-0 << HTTP/1.1 200

>> is sent, << received, and http-outgoing-N identifies the connection, so a new number for every request shows that connections are not being reused. For connection-pool questions (reuse, stale connections, time_to_live) use org.apache.http.impl.conn at DEBUG instead.

For TLS, the HTTP logs stop at the socket; the JVM’s own -Djavax.net.debug=ssl:handshake prints the handshake, as shown on HTTP Requests.

Wire logs contain everything: the Authorization: Bearer header, the login body with the password, cookies and every response body. Use them with one thread and placeholder credentials, never commit the log, and never enable them in a load run, where they multiply the disk I/O by the size of every request and response.

Catalogue of common failures

The table is the index; the sections after it give the evidence and the fix, and link to the page that covers the topic in full. Messages in monospace were reproduced on 5.6.3 or read from the source.

Symptom Cause Fix

java.lang.OutOfMemoryError in jmeter.log or on the console; the run slows down, then dies; an .hprof file appears

The heap (1 GiB by default) is too small for the plan: View Results Tree or other GUI listeners in a load run, large responses kept by many threads, too many threads for one JVM

Remove GUI listeners, write CSV results, raise HEAP, split the load over generators (OutOfMemoryError and heap)

Non HTTP response code: java.net.BindException (Cannot assign requested address on Linux) after some minutes of load

The generator ran out of ephemeral ports: a new connection per request leaves thousands of sockets in TIME_WAIT

Reuse connections, spread the load, widen the port range (java.net.BindException)

java.net.BindException: Address already in use at start-up of the recorder or jmeter-server

Another process holds the port (a second recorder, a running jmeter-server)

Stop the other process or choose another port (java.net.BindException)

Samples fail with Non HTTP response code: ... and no HTTP status

No HTTP response at all: connection refused, unknown host, timeout, TLS failure, or an invalid URL built by the plan

Read the exception name in the response code ("Non HTTP response code" errors)

Non HTTP response code: javax.net.ssl.SSLException or a handshake failure

TLS against a plain port, a protocol or cipher the server does not accept, or a client certificate the server requires

Match protocol, port and TLS settings; debug with javax.net.debug (TLS handshake failures)

While recording, the browser shows a certificate error on every HTTPS page; unknown_ca in jmeter.log

The browser does not trust the JMeter recorder’s root CA

Install ApacheJMeterTemporaryRootCA.crt in the browser’s store (The recorder unknown_ca error)

Connection refused to host or rmi_keystore.jks (No such file or directory); Following remote engines could not be configured

jmeter-server not running or not reachable, or RMI over SSL without a keystore on the client or the server

Start the servers, open the RMI ports, create and copy the keystore (RMI connection refused and keystore errors)

Summary shows 0 samples; Test failed! and File ... must exist and be readable in jmeter.log

The CSV Data Set Config cannot find its file

Fix the path (relative to the .jmx), copy the file to every generator (Empty CSV variables and wrong paths)

A whole CSV line lands in one variable; other variables are empty or keep old values

Wrong delimiter, header read as data, short lines

Set the delimiter and Ignore first line correctly (Empty CSV variables and wrong paths)

${bookId} appears literally in a URL, a body or a log; URISyntaxException: Illegal character in the response code

The variable was never set: misspelt name, extractor did not run, wrong scope, or a property read as a variable

Find where it should be set, check scope and spelling (${var} not substituted)

Requests carry TOKEN_NOT_FOUND or LOANID_NOT_FOUND; 401 after login

The extractor’s expression did not match, so it wrote its default value

Test the expression in the View Results Tree, fix it, assert on the default (Extractor defaults hit)

CannotResolveClassException: com.blazemeter... and the plan does not load; plugin elements missing from the Add menu

The plugin jar is not in lib/ext of this installation, a dependency is missing from lib, or JMeter was not restarted

Install the plugin with the Plugins Manager or install-for-jmx, restart (Plugin not loading)

Every gRPC sample fails with response code 400 and a parsing exception

The plugin could not compile the protos or resolve the method: bad proto root, missing import, misspelt method

Fix the proto root, library directory or full method (gRPC "method not found" and proto import errors)

Every gRPC sample fails with 12 UNIMPLEMENTED

The server has no such method: the test’s .proto and the deployed service differ

Use the contract of the deployed version (gRPC "method not found" and proto import errors)

OutOfMemoryError and heap

Symptom. java.lang.OutOfMemoryError with a reason: Java heap space (the heap is full), GC overhead limit exceeded (the JVM spends nearly all its time collecting), Metaspace (too many classes, typically JSR223 scripts that are not cached), or unable to create native thread (the operating system refused another thread). Before the error the summariser shows throughput falling while response times rise for every request at once, which is the generator, not the server.

Cause. The 5.6.3 launcher starts JMeter with HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" unless HEAP is set, and the Getting Started guide warns that 1 GB may not be enough for a plan and its thread count. The usual consumers are listeners that keep results in memory (View Results Tree, View Results in Table, Aggregate Report, the plugin graphs), large responses held by many threads at once, and XML results with response data.

Fix, in this order:

  1. Remove or disable every GUI listener for the load run and write results with -l to CSV; see Listeners & Results Files.

  2. Cache JSR223 scripts (no ${...} inside a cached script); see Scripting with JSR223 & Groovy.

  3. Raise the heap for the run, keeping -Xms equal to -Xmx and the total below the machine’s free memory:

    HEAP="-Xms4g -Xmx4g -XX:MaxMetaspaceSize=256m" \
      jmeter -n -t bookshelf-load.jmx -l load.jtl -j load.log -f
  4. For unable to create native thread, raise the process and file limits of the user (ulimit -u, ulimit -n) or use fewer threads per generator.

  5. When one JVM is still not enough, split the load over several generators.

Evidence. The launcher always adds -XX:+HeapDumpOnOutOfMemoryError, so the JVM writes a .hprof file (named after the process id) into the working directory when the heap is exhausted; open it in a heap analyser to see which objects filled it. During a run, bin/heapdump.sh asks a CLI run for a dump through the shutdown port without waiting for the error. Sizing the heap, thread stacks and containers is worked through on Running Tests in CLI Mode.

java.net.BindException

Two different problems share this exception.

At load, inside samples: Cannot assign requested address (Linux; Windows reports Address already in use: connect). Every new TCP connection needs a local ephemeral port, and a port stays in TIME_WAIT for 60 s after the client closes it. A plan that opens a new connection per request exhausts the default Linux range (32768 to 60999, 28,232 ports) at about 470 new connections per second sustained for a minute. The errors look like server failures, but they appear on the generator, at a constant rate once the threshold is crossed, and the server’s logs show no corresponding requests. The fix is to keep connections alive (Use KeepAlive, Same user on each iteration), then to spread the load, then to widen the port range; the arithmetic, the properties and the operating system commands are on Running Tests in CLI Mode.

At start-up of a listener: Address already in use. The HTTP(S) Test Script Recorder (default port 8888) or a jmeter-server (registry port 1099, or server_port) cannot open its port because another process already holds it, usually a previous recorder or server that was not stopped. Find the process (lsof -i :8888 on Linux and macOS, netstat -ano on Windows), stop it or choose another port. The UDP shutdown port of a CLI run (4445) is the exception: when it is taken, JMeter tries the next port, up to jmeterengine.nongui.maxport, and prints the one it used.

"Non HTTP response code" errors

When the HTTP sampler gets no HTTP response at all, it records the exception class as the response code, prefixed with Non HTTP response code:, and the exception message as the response message, prefixed with Non HTTP response message:. The class name is the diagnosis. These rows were reproduced on 5.6.3 (HttpClient4 implementation, Java 21) against local endpoints:

Response code and message Meaning Fix

org.apache.http.conn.HttpHostConnectException, Connect to localhost:9 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused

Nothing listens on that host and port, or a firewall rejected the connection. The message lists every address tried.

Check host and port (often a property that was not passed), whether the service is up, and whether the generator can reach it. Under load only: the server’s accept queue is full.

java.net.UnknownHostException, the host name followed by the resolver’s reason (wording depends on the operating system)

The host name does not resolve on this generator.

Check the name in HTTP Request Defaults and the host property; check DNS or /etc/hosts on the generator (containers often resolve differently from the laptop).

java.net.SocketTimeoutException, Read timed out

The connection was made but no response arrived within the Response timeout.

Under load this is a server saturation signal, not a plan bug; see Analyzing Results & Finding Bottlenecks. In a smoke run, the timeout is too low or the endpoint hangs.

javax.net.ssl.SSLException, Unsupported or unrecognized SSL message

Protocol is https but the port speaks plain HTTP.

Use http for that port, or the TLS port of the service (TLS handshake failures).

java.net.URISyntaxException, Illegal character in query at index 36: http://localhost:9/api/books?query=${term}

The Path contains a ${...} reference that was never resolved; $, { and } are not legal in a URL.

See ${var} not substituted.

org.apache.http.NoHttpResponseException (not reproduced here)

The server closed a kept-alive connection that the client reused.

Lower httpclient4.time_to_live or keep httpclient4.validate_after_inactivity (default 4900 ms) below the server’s keep-alive timeout.

A row that is the same for every sample from the first second is a configuration problem (address, protocol, URL). A row that appears only after load has risen (timeouts, refused connections, NoHttpResponseException) is a capacity signal for the server or a resource limit on the generator. The dashboard’s Errors table groups samples by these strings; the classification of HTTP status codes is on Analyzing Results & Finding Bottlenecks.

Retries hide these errors: httpclient4.retrycount defaults to 0 in 5.6.3, and should stay there for measurement.

TLS handshake failures

JMeter’s HTTP samplers accept every server certificate, valid or not, so an untrusted or expired server certificate is not a cause of failure (and a load test does not detect one). What remains:

Symptom Cause Fix

Unsupported or unrecognized SSL message

TLS against a plain HTTP port (reproduced above).

Correct Protocol or Port.

A handshake failure alert from the server (the alert name appears in the message, for example handshake_failure)

No TLS version or cipher suite in common: the server allows only TLS 1.3 and https.socket.protocols restricts the client to TLS 1.2, or the reverse.

Remove or align https.socket.protocols and https.cipherSuites; check the JVM’s own limits in its java.security.

The server rejects the connection after the handshake starts and asks for a certificate

Mutual TLS: the server requires a client certificate and none was sent.

Configure the keystore and the Keystore Configuration element, as on HTTP Requests.

Certificates does not conform to algorithm constraints

The server certificate uses an algorithm the JVM disables (MD2, short keys).

Fix the certificate in the test environment.

Keystore file not found, loading empty keystore (WARN in jmeter.log)

Logged by o.a.j.u.SSLManager at the first HTTPS request when no client keystore is configured.

Harmless unless the server needs a client certificate.

To see the handshake, add -Djavax.net.debug=ssl:handshake to a one-thread run; the JVM prints the protocol versions and cipher suites offered and the alert that ended the handshake. The TLS properties and the client-certificate setup are on HTTP Requests. gRPC over TLS has its own settings and fails with UNAVAILABLE when TLS is on one side only; see gRPC Load Testing.

The recorder unknown_ca error

While recording HTTPS traffic, the browser rejects the certificate the recorder presents for each site and jmeter.log shows unknown_ca: the browser does not trust the root CA that JMeter generates (ApacheJMeterTemporaryRootCA.crt in bin/). Install it as a trusted authority in the store the browser actually reads and restart the browser. The certificate is valid for 7 days by default and is regenerated with proxyserver.jks, so a recording that worked last week can fail today for the same reason. The full table of certificate symptoms (expiry, dynamic keys, keytool not found, a browser that refuses outright) is on Recording & cURL Import.

RMI connection refused and keystore errors

Distributed testing has its own failure modes, all of which happen before the first sample. These were reproduced on 5.6.3 with a client and a server on the same machine:

The server cannot start: no keystore. Since 4.0, RMI uses SSL unless server.rmi.ssl.disable=true, and the keystore defaults to rmi_keystore.jks (type JKS, alias rmi, password changeit). Without it, jmeter-server stops at once:

Server failed to start: java.rmi.server.ExportException: Listen failed on port: 0; nested exception is:
	java.io.FileNotFoundException: rmi_keystore.jks (No such file or directory)

The client cannot connect: no keystore on the client. The client needs the same keystore. Without it, -R fails for each server and the run exits with status 1:

Configuring remote engine: 127.0.0.1:1299
Exception creating connection to: 127.0.0.1; nested exception is:
	java.io.FileNotFoundException: rmi_keystore.jks (No such file or directory)
Failed to configure 127.0.0.1:1299
...
Error in NonGUIDriver java.lang.RuntimeException: Following remote engines could not be configured:[127.0.0.1:1299]

The client cannot connect: nothing listening. With a keystore (or with SSL disabled on both sides), a server that is not running, a wrong port or a firewall gives:

Configuring remote engine: 127.0.0.1:1299
Connection refused to host: 127.0.0.1; nested exception is:
	java.net.ConnectException: Connection refused
Failed to configure 127.0.0.1:1299

The fixes, which the distributed testing page of this section covers step by step:

  • Run bin/create-rmi-keystore.sh (or .bat) once, and copy the same rmi_keystore.jks into bin/ of the client and of every server, or point server.rmi.ssl.keystore.file at it. Disabling SSL with server.rmi.ssl.disable=true must be done on both sides, and only on a network you control.

  • Start jmeter-server on every node and check it listens on the registry port (1099 by default, or server_port).

  • Fix the ports for firewalls: server.rmi.localport for the server engine, client.rmi.localport for the objects the client exports to receive results (the manual notes that up to three ports from that value are used). With RMI over SSL, a fixed client.rmi.localport made every remote start fail in a 5.6.3 test run (Listen failed on port, then NotSerializableException: ...JMeter$ListenToTest); leave it at 0 in that case, as Distributed Testing explains.

  • A server whose host name resolves to a loopback address tells the client to call back to 127.0.0.1; start it with -Djava.rmi.server.hostname=<its reachable address>.

  • Use the same JMeter version on every node and, as the manual recommends, the same Java version, plus the same plugins and the same data files.

Empty CSV variables and wrong paths

Missing file. A CSV Data Set Config whose file does not exist does not produce failed samples: the thread stops at its first iteration, before any request. A 5.6.3 run of a plan whose Filename was data/users.csv with no such file printed a summary of zero samples, and jmeter.log held the reason:

summary =      0 in 00:00:00 = ******/s Avg:     0 Min: 9223372036854775807 Max: -9223372036854775808 Err:     0 (0.00%)
ERROR o.a.j.t.JMeterThread: Test failed!
java.lang.IllegalArgumentException: File users.csv must exist and be readable
	at org.apache.jmeter.services.FileServer.createBufferedReader(FileServer.java:424) ~[ApacheJMeter_core.jar:5.6.3]
	...
	at org.apache.jmeter.config.CSVDataSet.iterationStart(CSVDataSet.java:182) ~[ApacheJMeter_components.jar:5.6.3]

A zero-sample summary with Err: 0 therefore means "read the log", not "all good". Relative paths are resolved against the folder of the .jmx, not the working directory, and in a distributed run every server needs its own copy; see Parameterization & Test Data.

Empty or wrong values. The file is found but the values are wrong. Use a Debug PostProcessor on the first request and compare the variables with the file:

What the Debug output shows Cause Fix

username=reader01;placeholder-pw and password missing (reproduced)

The file uses ; but Delimiter is ,: the whole line is one value.

Set Delimiter to the file’s separator (\t for tab).

username=username on one iteration

Variable Names filled in and Ignore first line off on a file with a header.

Leave Variable Names empty to use the header, or tick Ignore first line.

username=<EOF>

End of file with Recycle on EOF off and Stop thread on EOF off.

Recycle, stop the thread, or provide enough rows.

A variable keeps the value of the previous iteration

A short line with fewer values than variable names.

Fix the data; keep files free of blank and short lines.

Garbled accented terms

Encoding mismatch.

Save the file as UTF-8 and set File Encoding to UTF-8.

${var} not substituted

JMeter does not report an undefined variable or a misspelt function: the Getting Started guide states that the reference "is just used as is". What happens next depends on where it is used. Both cases were reproduced on 5.6.3 with a variable term that was never set:

  • In the Path field: /api/books?query=${term} cannot be turned into a URI, so the sample fails before any connection with Non HTTP response code: java.net.URISyntaxException and Illegal character in query at index 36.

  • As a parameter value with URL Encode? ticked: the reference is encoded and sent, as /api/books?query=%24%7Bterm%7D. The server answers normally (often 200 with an empty result), so nothing fails unless an assertion checks the content.

  • In a JSON body: the literal ${term} is sent; the API either rejects it with 400 or searches for the text.

The causes, in the order they occur in practice:

Cause How to tell, and fix

Misspelt name or wrong case (bookID vs bookId)

Variable names are case-sensitive. Compare with the Debug PostProcessor output.

The setting element has not run yet

Execution order: a CSV Data Set Config in a sibling controller, an extractor on a later sampler. Move it; see Test Plan Elements, Execution Order & Scoping.

The setting element is out of scope

An extractor under another sampler, or in another Thread Group: variables are per thread. Share across threads with properties; see Properties, Variables & Functions.

A property read as a variable

${threads} instead of ${__P(threads,1)}: -Jthreads=50 sets a property, not a variable.

The extractor ran but did not match and has no default

The variable is not created, so the reference stays literal. Give every extractor a default (next section).

${...} inside a cached JSR223 script

The first value is compiled into the script. Use vars.get("term") instead; see Scripting with JSR223 & Groovy.

__threadNum or __Random in User Defined Variables

Evaluated once, by another thread. Use a CSV file, a Counter or a preprocessor.

To find every element that uses a variable, use the Search menu with the variable name: matching elements are highlighted in the tree.

Extractor defaults hit

When the Bookshelf plan sends Authorization: Bearer TOKEN_NOT_FOUND or calls /api/loans/LOANID_NOT_FOUND, the extractor ran and did not match. That is the purpose of a loud default value; the alternative is the silent literal of the previous section. The usual causes:

  • The response is not what you think: an error page, a 401, an HTML login page from a proxy. Check the login sample’s response code before the expression.

  • The expression is wrong: $.accessToken where the API returns token. Paste it into the JSON Path Tester on the real response.

  • The extractor looks at the wrong part: Field to check set to the body when the value is in a header (the new loan id in Location), or Apply to set to sub-samples only.

  • The match number: 0 picks a random match, and -1 writes the numbered variables (${loanId_1}) and loanId_matchNr, while ${loanId} itself is the default, the first match or a stale value depending on the extractor.

Make the failure fail loudly: a Response Assertion on the variable that fails when it equals the default, and a Result Status Action Handler that starts the next iteration. The defaults, the -1 rules and the assertion are on Correlation & Extractors and Assertions.

Plugin not loading

Symptom: the plan does not load. A .jmx that uses a plugin element names the plugin’s class. When that class is not on JMeter’s classpath, the plan cannot be read at all. Reproduced on 5.6.3 with the Concurrency Thread Group of jpgc-casutg not installed:

Error in NonGUIDriver java.lang.IllegalArgumentException: Problem loading XML from:'.../bookshelf-stress.jmx'.
Cause:
CannotResolveClassException: com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup

 Detail:com.thoughtworks.xstream.converters.ConversionException:
---- Debugging information ----
...
path                : /jmeterTestPlan/hashTree/hashTree/com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup
line number         : 6
version             : 5.6.3

The class name tells which plugin is missing; path and line number point at the element in the .jmx. In the GUI the same error appears in a dialog when opening the file.

Symptom: the plugin is installed but its elements are not in the Add menu, or a sample fails with NoClassDefFoundError.

Cause Fix

JMeter was not restarted after the jar was added

Class loaders are built at start-up: restart.

The jar is in lib instead of lib/ext

Only lib/ext (and search_paths) are scanned for elements; move it.

A library the plugin needs is missing from lib

NoClassDefFoundError names the class. Install with the Plugins Manager, which resolves libraries, or copy the dependency into lib.

Two versions of a plugin or library on the classpath

Remove the older jar; with the Plugins Manager, uninstall and reinstall.

The plugin is installed on the client but not on a jmeter-server node

Install the same plugins at the same versions on every node.

The plugin is not in the Plugins Manager dialog after a catalogue update

Delete the pmgr_cache file in the java.io.tmpdir directory and reopen the dialog.

The Plugins Manager refuses to apply changes

The user running JMeter cannot write to its directories; install JMeter in a directory that user owns.

In a Maven build, the plugin is not in the build’s JMeter

List it in <jmeterExtensions> of the jmeter-maven-plugin and check which JMeter version the plugin builds with.

bin/PluginsManagerCMD.sh install-for-jmx bookshelf-stress.jmx reads the class names from the plan and installs the plugins that provide them, which fixes the first symptom on a CI agent or in a Docker image. All of this, including the discovery mechanism in 5.6 and the lib/lib/ext rules, is on Plugins & Plugins Manager.

gRPC "method not found" and proto import errors

The gRPC Request plugin (2.0.2) fails in two places, and its response code says which:

  • response code 400: the request could not be prepared on the generator. The response message is GRPCSampler parsing exception: An unknown exception occurred before the GRPC request was initiated..., the response data holds the stack trace, and the sample takes almost no time;

  • response code 500 with a gRPC status such as 12 UNIMPLEMENTED in the response message: the call reached the server, which answered with an error.

The messages below are those of the 2.0.2 source (ServiceResolver, ProtoMethodName, ProtocInvoker); they appear in the stack trace of the sample’s response data and in jmeter.log.

Message Cause Fix

Unable to find method CreateLoans in service LoanService

The service was found in the compiled protos but the method name in Full Method is not in it (here a misspelling of CreateLoan).

Click Listing…​ next to Full Method and pick the method from the drop-down; names are case-sensitive.

Unable to find service with name: LoanServce

No .proto under Proto Root Directory declares that service in that package: misspelt service, wrong package (bookshelf.v1 vs bookshelf), or the file is not under the proto root.

Use the full form package.Service/Method, for example bookshelf.v1.LoanService/CreateLoan, as Listing…​ shows it.

Could not extract method name from ... or Could not extract full service from ...

Full Method is not in the form package.Service/Method: a missing /, a . instead of /, or an unresolved ${...}.

Fix the format.

Protoc invocation failed with status: 1, followed by [Protoc error log] lines in jmeter.log

protoc could not compile a .proto under the proto root: an import that cannot be found, a syntax error, or an unrelated broken file in the same folder tree (every file below the root is compiled).

Read the [Protoc error log] lines, which name the file and the import. Put only the needed contracts under the root; add the folder that holds imports such as google/api/annotations.proto to Library Directory as an absolute path. Well-known types (google/protobuf/timestamp.proto) are bundled.

Could not find dependency: ...

A pre-compiled descriptor set (.bin) was built without its imports.

Rebuild it with protoc --include_imports.

Caught exception while parsing request for rpc

The request JSON is not valid, often because an unquoted variable was empty or unresolved.

Quote variables inside JSON strings and check the Request tab.

Response message 12 UNIMPLEMENTED (code 500)

The server does not implement the method: the test’s .proto is newer or older than the deployed service, or the request reached a different service behind a proxy.

Use the contract of the deployed version; regenerate descriptor sets whenever the .proto changes.

Two gRPC failures look like server problems but are not: 14 UNAVAILABLE on every call after one earlier error (in 2.0.2 a failed call closes the thread’s channel and it is not rebuilt), and 4 DEADLINE_EXCEEDED under load because the Deadline field kept its GUI default of 1000 ms. The plugin’s fields, its error mapping and these pitfalls are on gRPC Load Testing.

Other messages worth recognising

Message Meaning

WARN StatusConsoleListener The use of package scanning to locate plugins is deprecated... on the console

Log4j start-up noise in 5.6.3, not a JMeter error. Its removal is planned for 6.0.

WARNING: package sun.awt.X11 not in java.desktop on macOS

A JVM warning caused by an option in the launcher; harmless.

Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445

Normal: the UDP port for shutdown.sh and stoptest.sh.

The summariser shows Err: 100.00% but the process exit code is 0

Expected: the exit code reflects whether the run could start, not whether samples passed. Gate CI on the results, as on Running Tests in CLI Mode.

Error: Duplicate options for -j/--jmeterlogfile found.

-j passed to jmeter-server, whose script already sets it; drop it or call jmeter -s directly.

Load-generator cost of debugging

Every tool on this page is meant for one thread, not for the load run:

Tool Cost if left in a load run

View Results Tree

Keeps up to view.results.tree.max_results samples with their bodies in memory and refreshes the GUI; the documentation says it must not be used during a load test.

Debug Sampler

One extra sample per iteration, counted in throughput and the dashboard, with a body of every variable.

Debug PostProcessor

A sub-sample per parent sample; with JMeter Properties ticked, hundreds of lines each time.

Save Responses to a file

Nothing while samples succeed; one file per failure, and one per sample when everything fails.

DEBUG logging

Several log lines per sample; wire logging writes every byte sent and received to disk.

javax.net.debug

Prints every handshake and record; makes TLS throughput meaningless.

The safe pattern is to keep debugging elements disabled in the committed plan and enable them only in the GUI, or to gate them on a property (an If Controller on ${__P(debug,false)}) so that -Jdebug=true switches them on for a smoke run.

Pitfalls

  • Trusting the exit code. A run where every sample failed still exits with 0; read the summariser and the JTL.

  • Trusting a quiet summary. 0 in 00:00:00 with Err: 0 means threads died before sampling, usually a missing file.

  • Debugging under load. Errors seen only at 50 threads cannot be diagnosed with View Results Tree; reproduce the data or correlation part with one thread and look at capacity with the dashboard and server metrics.

  • Leaving DEBUG on. A -L...=DEBUG copied into a CI script turns the generator’s disk into the bottleneck.

  • Sharing logs and debug output. Wire logs, Debug Sampler output and XML results with request data contain tokens and passwords; keep them out of tickets and repositories.

  • Fixing the symptom. Raising the heap for a View Results Tree left in the plan, or adding retries for NoHttpResponseException, makes the run finish and the results wrong.

  • Assuming the books' advice still applies. log_level.* properties in jmeter.properties, BeanShell debug prints and plain RMI without a keystore are from before 3.2 and 4.0.

From the books to 5.6 to 6.0

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

Logging configuration

log_level.* properties in jmeter.properties (Pro ch. 8)

log4j2.xml since 3.2; -L takes full category names, with the jmeter and jorphan short forms kept for compatibility

Log viewer

Log viewer in the GUI

Options → Log Viewer, Options → Log Level, an error counter in the toolbar

Heap

Edit HEAP in the launcher script (Pro ch. 8)

HEAP set through the environment or setenv.sh; heap dump on OutOfMemoryError always on; heapdump.sh for a dump on demand

Remote exceptions

RMI without SSL; connection refused usually meant firewall or host name (Pro ch. 8)

RMI over SSL by default since 4.0, so a missing rmi_keystore.jks is now the most common distributed start-up error

Debugging elements

Debug Sampler, View Results Tree, BeanShell for printing values

Same elements, plus the JSON Path, JMESPath, Boundary and XPath2 testers; JSR223 + Groovy with the log binding instead of BeanShell

Plugins

Unzipped plugin sets (Cookbook appendix)

Plugins Manager and install-for-jmx; 5.6 loads plugins through ServiceLoader and still scans old jars

Java and start-up warnings

Java 7 or 8

Java 8+ for 5.6.3, 17+ recommended; 6.0 plans to require Java 17 and to remove the Log4j package-scanning warning

References