Apache JMeter works by turning a test plan into Java threads. Each thread in a Thread Group is one virtual user that walks through the plan’s samplers in order, sends a real protocol request, waits for the full response and records a sample: timings, bytes, status and pass or fail. Listeners and a results file collect the samples, and the HTML report is built from them afterwards. JMeter works at protocol level; it is not a browser.
Key takeaways
- One thread is one virtual user. Every thread runs the whole plan, independently of the others, and with no timer it sends requests back to back.
- Elapsed time runs from just before the request is sent to just after the last byte of the response arrives; latency stops at the first byte.
- JMeter does not run JavaScript or render pages. It can fetch the resources a page references, but only if you ask it to.
- In distributed mode every worker runs the full plan, so threads multiply by the number of workers rather than being split between them.
How does JMeter work, step by step?
A JMeter run goes through the same seven steps every time, whether you start it from the GUI or the command line. The test plan is loaded, threads are started, each thread walks the element tree, samplers send requests, results are recorded, and reports are written at the end.
| Step | What happens | Where you control it |
|---|---|---|
| 1. Load the plan | JMeter reads the .jmx file, an XML tree of elements | -t plan.jmx or File > Open |
| 2. Start threads | Each Thread Group starts its threads, spread over the ramp-up period | Number of Threads, Ramp-up, Loop Count |
| 3. Walk the tree | Each thread processes elements in a fixed order around every sampler | Element position in the tree |
| 4. Send the request | The sampler sends a request over the real protocol and waits for the response | HTTP Request, JDBC Request and other samplers |
| 5. Record a sample | Timings, sizes, status code and pass or fail are stored as one sample | Assertions decide pass or fail |
| 6. Collect results | Listeners and the results file receive every sample; a summary prints to the console | -l results.jtl, listeners |
| 7. Build the report | The HTML dashboard is generated from the results file | -e -o report |
This page explains the mechanism behind each step. If you are new to JMeter, the beginner’s path through JMeter puts the same pieces in learning order, and the JMeter load testing tutorial applies them to a complete test.
What is a JMeter thread, and how does it act as a user?
A JMeter thread is a Java thread that plays one virtual user from start to finish. The JMeter manual says each thread “will execute the test plan in its entirety and completely independently of other test threads”. It keeps its own cookies and cache, runs its own loops, and knows nothing about the other threads.
The Thread Group decides how many threads start and when. With 10 threads and a 100-second ramp-up, the manual’s example, JMeter takes 100 seconds to get all 10 running, starting one every 10 seconds. By default a thread loops once through its elements and, in the manual’s words, “executes samplers in sequence without pausing”. Pauses only exist if you add a timer.
Within each pass, the order is fixed: configuration elements, pre-processors, timers, the sampler, post-processors, assertions, then listeners. Where an element sits in the tree decides which samplers it applies to, which is covered in the execution order of elements in JMeter.
Because every virtual user is a real thread with its own memory, the machine running JMeter sets the ceiling on how many users it can simulate. How to find that ceiling for your own hardware is in how many concurrent users JMeter can handle.
What happens when a sampler runs?
The sampler opens a connection (or reuses one), sends the request, waits until the whole response has arrived and stores the result as a sample. For HTTP the default implementation is HttpClient4. Nothing is simulated at this point: the server receives a real request and does real work.
JMeter’s glossary defines the three timings each HTTP sample carries:
- Elapsed time: “from just before sending the request to just after the last response has been received”.
- Latency: “from just before sending the request to just after the first response has been received”.
- Connect time: “the time it took to establish the connection, including SSL handshake”.
Here is one sample from a CLI run we made against a local web server on JMeter 5.6.3 (3 threads, 4 loops, two requests each):
timeStamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect
1792864321231,19,GET home,200,OK,Users 1-1,text,true,,240,128,1,1,http://127.0.0.1:18765/index.html,16,0,12
Elapsed is 19 ms, latency 16 ms and connect 12 ms: the connection set-up is counted inside latency, and latency inside elapsed. This was the first request of the whole run; the other 23 samples took 0 to 1 ms each, as the console summary below shows.
The second request in that plan asked for a page that does not exist. The server answered 404 and JMeter marked every one of those samples success=false without any assertion on it, so the run ended with 12 errors out of 24 samples. That is the built-in rule: in JMeter 5.6.3 an HTTP status from 200 to 399 counts as a success and anything else as a failure. Assertions add your own rules on top, for example failing a 200 response whose body does not contain the expected text. The guide to JMeter assertions shows the common ones.
Is JMeter a browser?
No. The JMeter home page says it plainly: “JMeter is not a browser, it works at protocol level.” It does not execute the JavaScript in a page and does not render HTML, so it measures how long the server takes to return responses, not how long a browser takes to show the page.
Some browser behaviour can be added with elements. The HTTP Cookie Manager “stores and sends cookies just like a web browser”, with a separate store for each thread. The HTTP Cache Manager simulates a browser cache, again per thread. Ticking Retrieve All Embedded Resources on an HTTP Request makes JMeter parse the returned HTML and request the images, scripts and stylesheets it references.
We re-ran the same plan with that option on. Each page sample then came back as a parent with numbered children:
elapsed,label,responseCode,success,URL
43,GET home,200,true,http://127.0.0.1:18765/index.html
18,GET home-0,200,true,http://127.0.0.1:18765/index.html
2,GET home-1,200,true,http://127.0.0.1:18765/logo.png
The parent GET home covers the page and its image together; -0 is the HTML and -1 the image. The console summary still counted 24 samples, one per parent. What JMeter cannot do is run the JavaScript that loads more content after the page arrives. When you need real browser timings under load, the WebDriver Sampler drives a real browser from JMeter, at a much higher cost per user.
Where do JMeter results go?
Every sample goes to the listeners in scope and, when you pass -l, to a results file. The file is CSV by default (jmeter.save.saveservice.output_format=csv), one row per sample, with the columns shown above. In CLI mode JMeter also prints a running summary to the console, every 30 seconds by default (summariser.interval).
The console output of our run looked like this:
Created the tree successfully using plan.jmx
Starting standalone test @ 2026 Oct 24 20:52:00 EEST (1792864320988)
summary + 1 in 00:00:00 = 10.3/s Avg: 19 Min: 19 Max: 19 Err: 0 (0.00%) Active: 1 Started: 1 Finished: 0
summary + 23 in 00:00:02 = 11.9/s Avg: 0 Min: 0 Max: 1 Err: 12 (52.17%) Active: 0 Started: 3 Finished: 3
summary = 24 in 00:00:02 = 11.8/s Avg: 1 Min: 0 Max: 19 Err: 12 (50.00%)
Tidying up ... @ 2026 Oct 24 20:52:03 EEST (1792864323183)
... end of run
summary + lines cover the interval since the last line, summary = the whole run so far. With -e -o report, JMeter then reads the results file and writes the HTML dashboard into the report folder.
Listeners in the GUI hold results in memory and redraw as samples arrive. JMeter’s best-practices page says not to use View Results Tree or View Results in Table during a load test and to keep listeners to a minimum; with -l they can all be disabled.
How does JMeter work across several machines?
In distributed mode one JMeter instance, the controller, sends the test plan to several workers running jmeter-server and collects their results. They talk over Java RMI. Each worker runs the complete plan: the manual says “JMeter does not distribute the load between servers, each runs the full test plan”, so 1,000 threads on 6 workers means 6,000 threads.
Workers send their samples back to the controller in batches by default (the StrippedBatch mode, which drops response data from successful samples). Any CSV files the plan reads must exist on every worker. The full setup, with ports, keystore and the common failures, is in JMeter distributed testing, step by step.
Running the same test plan in the cloud
The mechanism is the same when someone else runs the machines. LoadFocus JMeter load testing takes your .jmx file and its CSV data, starts cloud load generators for each run, spreads your threads across them and shuts them down when the test ends. You pick virtual users, duration and ramp-up per run without editing the script, choose from 25 cloud regions, and get percentile response times, throughput and errors per request and per location.

Frequently Asked Questions
How does JMeter simulate users?
Each virtual user is a Java thread created by a Thread Group. The thread runs the whole test plan on its own, sending real requests and waiting for each response, with its own cookies and cache. Timers add the pauses a real user would take between requests.
What is the difference between elapsed time and latency in JMeter?
Latency stops when the first part of the response arrives; elapsed time stops when the last part has arrived. Both start just before the request is sent and both include connect time. A large gap between them means a large or slowly streamed response body.
Does JMeter run JavaScript or render pages?
No. JMeter works at protocol level, so it never runs a page’s JavaScript and never renders HTML. It can download the images, scripts and stylesheets a page references when Retrieve All Embedded Resources is ticked, but content loaded by JavaScript is not requested.
Does JMeter treat a 404 or 500 response as an error?
Yes, by default. JMeter 5.6.3 treats status codes 200 to 399 as success and everything else as failure; in our run every 404 was recorded with success=false without any assertion. A 200 response is a success unless an assertion fails it, which is why error pages that return 200 need a content check.
Why turn listeners off for a real run?
Because they hold samples in memory and update the GUI while the test runs, using CPU and memory the load generator needs. Use them while building the plan, then run in CLI mode with -l to write results to a file and build the HTML report from it.
Do JMeter workers split the threads between them?
No. In distributed mode each worker runs the full test plan, so the total number of threads is the per-plan number times the number of workers. Divide the thread count yourself if you want a fixed total.
Sources
- Apache JMeter, home page (“JMeter is not a browser, it works at protocol level”), read 24 October 2026.
- Apache JMeter user manual, Elements of a Test Plan (thread independence, ramp-up example, timers, execution order), read 24 October 2026.
- Apache JMeter user manual, Glossary (elapsed time, latency, connect time), read 24 October 2026.
- Apache JMeter user manual, Properties Reference (output format csv, summariser interval 30 s, HttpClient4 default, StrippedBatch), read 24 October 2026.
- Apache JMeter user manual, Component Reference (HTTP Cookie Manager, HTTP Cache Manager, embedded resources), read 24 October 2026.
- Apache JMeter user manual, Remote Testing (RMI, each server runs the full plan), read 24 October 2026.
- Apache JMeter user manual, Best Practices (listeners during load tests), read 24 October 2026.
- Our own runs: JMeter 5.6.3 (archive checked against its SHA-512), OpenJDK 11, CLI mode against a local Python web server, 24 October 2026. The 200-399 success range is read from
MetricUtils.isSuccessCodein that release’sApacheJMeter_core.jar. - LoadFocus, JMeter load testing (upload, cloud generators, 25 regions, per-request percentiles), read 24 October 2026.