Introduction & Architecture

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.

Baseline date: 2026-10-14. This page was written against Apache JMeter 5.6.3, the latest release on that date (released 2024-01-07). JMeter 6.0 is not released; the changes planned for it on the master branch of the project are described as planned, never as shipped behaviour. JMeter 5.6.x runs on Java 8 or later, but the examples use Java 17 or later (21 recommended), which is also what the 6.0 branch requires. Other tools that appear later in the section are the community JMeter Plugins Manager 2.0 (an independent catalogue, not an Apache project) and the jmeter-maven-plugin 3.8.x. This page is the map of the section: what JMeter is, how a test plan turns into load, which pieces carry the results back, and which element families exist. Details of every piece are in the pages that follow. The examples use the Bookshelf API of the section landing page, always aimed at localhost.

What JMeter is

Apache JMeter is an open source Java application that generates traffic from many simulated users and measures how the system under test responds. The project home page describes it as a 100% pure Java application designed to load test functional behavior and measure performance. It was first written for web applications and now covers much more. It is licensed under the Apache License 2.0 and is a top-level project of the Apache Software Foundation.

Three properties explain most of what JMeter can and cannot do.

  • Pure Java. JMeter runs wherever a compliant Java runtime runs, with no native component. Installation is unpacking an archive. The price is that every virtual user costs JVM memory and CPU, which limits how many fit in one process (see Load generator cost).

  • Protocol level. JMeter sends requests and reads responses on the wire. It does not drive a browser.

  • Test plan as data. A test is a tree of test elements stored as XML in a .jmx file. The same file can be edited in a GUI, run from a command line, versioned in Git and executed by a CI job.

JMeter is not a browser

The official documentation is explicit: JMeter works at protocol level. For a web application it can mimic what a browser sends (headers, cookies, form posts, embedded resources), but it does not perform all browser actions. In particular it does not run the JavaScript found in HTML pages and it does not render pages, so the time a browser would spend executing scripts and painting the page is not part of any sample. A JMeter response time is the time to send a request and receive the response, for an application whose front end does heavy client-side work that is only a part of what a user feels.

This has two practical consequences. First, single-page applications must be tested at the API level: the plan calls the JSON endpoints the front end calls (for Bookshelf, GET /api/books?query= and POST /api/loans), not the page that displays them. Second, front-end performance (rendering, script execution, Core Web Vitals) needs a browser-driven tool; the Playwright, Cypress and Selenium material of this documentation (tracked as issue #258) covers browser automation, and the two approaches complement each other.

Protocols in core and through plugins

The table lists what the official site and the user manual name as supported by a standard installation, and what needs an external jar or a community plugin. Anything not in the first rows should be treated as unsupported in core.

Protocol or area Core JMeter 5.6.3 Remarks

HTTP and HTTPS

HTTP Request sampler (HttpClient4 or Java implementation), GraphQL HTTP Request

Web applications on any server technology, REST and SOAP over HTTP.

JDBC databases

JDBC Connection Configuration and JDBC Request

No driver is bundled: add your vendor’s jar to lib/.

Messaging

JMS Point-to-Point, JMS Publisher, JMS Subscriber

The JMS API is included, a provider client is not: add the provider’s jars.

Mail

SMTP Sampler, Mail Reader Sampler

SMTP(S), POP3(S), IMAP(S).

Other classic protocols

TCP, FTP, LDAP and extended LDAP, OS Process, Java Request, JUnit Request, Bolt (Neo4j), Access Log Sampler

Native commands and shell scripts through the OS Process sampler.

gRPC

Not in core

A community plugin, covered in its own page, handles unary calls only.

WebSocket

Not in core

Provided by community plugins.

HTTP/2

Not in core

Provided by a community plugin (bzm-http2 in the Plugins Manager catalogue).

Kafka, MQTT

Not in core

Provided by community plugins.

How JMeter works

The execution model in one picture

JMeter architecture: GUI or CLI mode loads a test plan, the engine starts thread groups, samplers send requests and produce results that listeners write to the JTL file, the HTML dashboard and the Backend Listener

The figure reads from top to bottom. A test plan (the .jmx file) is opened either in GUI mode, to design and debug it, or by CLI mode, to run it. The engine inside one JVM reads the tree and starts the thread groups. Each thread of a thread group is one virtual user and repeatedly walks the plan: every sampler sends a request to the system under test and the response is turned into a result. Results flow to the listeners in scope, which display them or store them in a JTL file. The HTML dashboard is generated from that file after (or, with -g, long after) the run, and the Backend Listener can stream the same numbers to a time-series database while the test is running.

One Java thread per virtual user

JMeter uses a thread-per-user model. A thread group is configured with a number of threads, and each one is a real Java thread that executes the elements below the thread group in tree order, one after another, completely independently of the other threads. Within a thread the work is synchronous: a thread sends a request and blocks until the response arrives (or a timeout fires), then runs the next element. The official test plan page states it this way: each thread executes the test plan in its entirety and completely independently of other test threads.

Several consequences follow from this model, and later pages build on each of them.

  • Concurrency is bounded by threads. To keep 500 requests in flight at once you need 500 threads. The number of requests per second is an outcome of thread count, response time and think time, not a setting (unless a throughput element or the Open Model Thread Group is used).

  • Slow responses reduce the load. When the system under test slows down, every thread waits longer and sends fewer requests. This is the closed workload model, and it is the reason the section later explains coordinated omission and the open model.

  • Per-thread state. Variables, cookies (through the Cookie Manager) and the results of extractors live per thread. Properties, by contrast, are global to the JVM. The difference is the subject of the page on properties, variables and functions.

  • Thread groups are parallel by default. Several thread groups in one plan start together. The Test Plan element has an option to run them one at a time, and the special setUp and tearDown thread groups run before and after the regular ones.

A test plan is a tree

A test plan is a hierarchy of test elements. The position of an element in the tree defines what it applies to: a config element, timer, assertion or listener affects the samplers in its branch (its scope), and a timer or assertion placed at the top level applies to every sampler below it. For each sampler JMeter processes the elements in scope in a fixed order.

Step What runs

0

Configuration elements

1

Pre-processors

2

Timers

3

The sampler itself

4

Post-processors (unless the SampleResult is null)

5

Assertions (unless the SampleResult is null)

6

Listeners (unless the SampleResult is null)

Logic controllers and samplers run in tree order, and timers, assertions and processors run only when a sampler they apply to is present. Because the order is fixed by element type and not by position among siblings, putting an assertion above its sampler in the tree makes no difference. The dedicated page on execution order and scoping works through the rules and their exceptions.

A minimal saved plan looks like this. The root element carries the JMeter version that wrote it, and every element is followed by a hashTree holding its children.

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Bookshelf smoke" enabled="true">
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Readers" enabled="true">
        <stringProp name="ThreadGroup.num_threads">1</stringProp>
        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController"
                     guiclass="LoopControlPanel" testclass="LoopController">
          <stringProp name="LoopController.loops">3</stringProp>
          <boolProp name="LoopController.continue_forever">false</boolProp>
        </elementProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Search books" enabled="true">
          <stringProp name="HTTPSampler.domain">localhost</stringProp>
          <stringProp name="HTTPSampler.port">8080</stringProp>
          <stringProp name="HTTPSampler.path">/api/books?query=java</stringProp>
          <stringProp name="HTTPSampler.method">GET</stringProp>
        </HTTPSamplerProxy>
        <hashTree/>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

The fragment is shortened to the properties that matter here; JMeter itself writes more properties (for example HTTPSampler.follow_redirects) and the exact set depends on the version. Always create plans through the GUI or a generator and treat the XML as something to read and review, not to hand-write.

From sampler to result

When a sampler finishes it returns a SampleResult: an object recording the sampler label, the start time, the elapsed time, the latency (time to the first byte), the connect time, the response code and message, whether the sample was successful, the number of bytes and the response data. The post-processors and assertions of that sampler run against the result (an assertion failure marks the sample as failed), and then every listener in scope receives it. A listener is just a consumer of results, so the same stream can be shown in a tree, aggregated into a table, written to a file and sent to a database at once. Listeners are therefore also the main source of load-generator overhead: see the next section.

The sample file written by a listener or by the -l option of the command line is called a JTL file. By default it is a CSV file with a header line, one row per sample, and the columns are controlled by jmeter.save.saveservice.* properties. An XML format exists, but it is far larger and is not used for load tests. The HTML dashboard is generated from a CSV JTL file, which is why the later page on results files treats the column configuration carefully.

GUI mode and CLI mode

JMeter has two ways to start, and the official documentation gives a strict division of labour between them.

Aspect GUI mode CLI mode (-n)

Purpose

Create, debug and validate the plan, record scripts, run a few users to check it

Run the actual load test

Started with

bin/jmeter (or bin/jmeter.bat)

jmeter -n -t plan.jmx -l results.jtl

Output

Live in the listeners shown in the window

JTL file, log file, optional dashboard

Resource use

Swing UI and in-memory listener data, so heavy

Light, no UI

Official advice

Use it for building the plan: "Don’t run load test using GUI mode !"

The mode for load testing, because it gives optimal results

The user manual’s wording is that GUI mode should only be used for creating the test script and CLI mode for load testing, and the same chapter lists the basic command-line options: -n runs in CLI mode, -t names the .jmx file, -l names the JTL file that sample results are logged to, -j names the JMeter run log, -e generates the report dashboard after the load test and -o names the output folder for it (which must not exist or must be empty). A first run of a three-iteration smoke plan against a local Bookshelf API looks like this:

bin/jmeter -n -t bookshelf-smoke.jmx -l smoke.jtl -j smoke.log

and prints a summary of this shape (representative output; counts and times depend on the machine and the target):

Creating summariser <summary>
Created the tree successfully using bookshelf-smoke.jmx
Starting standalone test @ 2026 Oct 14 10:30:00 CEST (1791966600000)
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary =      3 in 00:00:01 =    3.0/s Avg:    24 Min:    11 Max:    52 Err:     0 (0.00%)
Tidying up ...    @ 2026 Oct 14 10:30:01 CEST (1791966601000)
... end of run

The books call the second mode "non-GUI mode" (Pro ch. 3 and 5); current documentation calls it CLI mode, and this section uses that name. Installation and the full set of command-line options are covered in later pages.

The element families

Every node of the tree is a test element belonging to one of a few families. The mind map shows the families and the members that matter most; the table below it says what each family does and which later page treats it.

mindmap root((Test plan)) Thread groups Thread Group setUp and tearDown Open Model Thread Group Samplers HTTP Request JDBC Request JMS and Mail TCP and others Logic controllers Loop and If Transaction Module and Include Config elements CSV Data Set HTTP Defaults Managers Pre and post processors JSR223 Extractors Timers Constant and Uniform Throughput timers Assertions Response and JSON Duration and Size Listeners Simple Data Writer Backend Listener Summary Report Test fragments Module Controller Include Controller
Family What it does Examples

Test Plan

The root. Holds user defined variables, the option to run thread groups serially, the tearDown option and extra classpath entries.

Test Plan

Thread groups

The starting point of any test: define how many virtual users run, how fast they start and how long.

Thread Group, setUp Thread Group, tearDown Thread Group, Open Model Thread Group (experimental)

Samplers

Send a request to a server and wait for the response; each produces a SampleResult.

HTTP Request, JDBC Request, JMS Publisher, SMTP Sampler

Logic controllers

Change the order or repetition of their children.

Loop, If, Transaction, Interleave, Once Only

Config elements

Add to or modify requests; they apply to their branch of the tree. User Defined Variables is the exception and is processed at the start of the test.

CSV Data Set Config, HTTP Request Defaults, Header Manager, Cookie Manager

Pre-processors

Run before a sampler, often to change request settings or set variables.

JSR223 PreProcessor, User Parameters

Post-processors

Run after a sampler, often to extract values from the response.

JSON Extractor, Regular Expression Extractor

Timers

Add a delay before each sampler in their scope.

Constant Timer, Uniform Random Timer, Constant Throughput Timer

Assertions

Check that a response contains expected content; a failed check marks the sample failed.

Response Assertion, JSON Assertion, Duration Assertion

Listeners

Show or save the data collected during the run.

View Results Tree, Summary Report, Simple Data Writer, Backend Listener

Test fragments

Reusable blocks that run only when referenced by a Module Controller or Include Controller.

Test Fragment

The Test Plan element

The root element has a small set of settings that affect the whole run.

Setting Effect

User Defined Variables

Static variables for values repeated throughout the test, such as a server name. A name defined more than once takes the last definition read from top to bottom. The Test Plan itself cannot refer to variables it defines; use a User Defined Variables element to build variables from other variables.

Run Thread Groups consecutively

Runs the thread groups serially instead of in parallel (the TestPlan.serialize_threadgroups property in the .jmx).

Run tearDown Thread Groups after shutdown of main threads

When selected, tearDown groups run after a graceful shutdown of the main threads. They do not run if the test is forcibly stopped.

Functional Test Mode

Saves response data and sampler data for every sample to all result files. This increases the resources needed and can hurt performance, and it does not affect CSV result files, which cannot store such data. Keep it off for load tests.

Add directory or jar to classpath

Adds jars or directories to the JMeter loader for this plan (additive; removing an entry requires a restart). Useful for utility jars such as JDBC drivers, but it cannot add GUI plugins. The properties user.classpath and plugin_dependency_paths do the same globally.

The setUp and tearDown thread groups behave exactly like normal thread groups, except that setUp threads run before the regular thread groups and tearDown threads after they finish. A typical Bookshelf use is a setUp group that creates a test user and a tearDown group that deletes the loans created during the run.

Load generator cost

Because every virtual user is a thread and every sample produces an object, the machine running JMeter is a resource that must be managed like the system under test. A load test whose generator is saturated measures the generator, not the server. The main levers, which later pages treat in detail, are:

  • run in CLI mode and never load test from the GUI;

  • keep the number of listeners and the amount of saved data small, because each listener holds or writes every sample;

  • leave Functional Test Mode off and do not save response bodies for load tests;

  • use few assertions on large responses, since each one inspects the body;

  • write results as CSV and generate the dashboard afterwards;

  • size the heap to the load: the default heap of the launcher scripts is -Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m, changed through the HEAP or JVM_ARGS environment variables or bin/setenv.sh;

  • when one machine cannot generate the required load, distribute the test over several JMeter servers, remembering that every server runs the whole plan.

The official user manual does not give a figure for how many threads fit on one machine, because it depends on the plan, the responses and the hardware; measure it with the actual plan.

Where JMeter fits

Functional tests and load tests

Unit and integration tests answer whether an answer is correct for one caller. Load tests answer how the system behaves when many callers arrive together: latency percentiles, throughput, error rate and saturation. JMeter is built for the second job, but because it has assertions, extractors, logic controllers and JUnit and JDBC samplers it can also run functional or integration checks: the Test Plan element even has a Functional Test Mode that records full request and response data for that purpose. The two uses should not share a plan. A functional plan has one user, many assertions and saves everything; a load plan has many users, few assertions and saves the minimum.

For Bookshelf, one .jmx file can hold both a journey with strict assertions run by a single user in CI as a smoke test, and the same journey with relaxed assertions run by 50 users. The section builds exactly that, as the smoke profile in the first case and the load profile in the second.

Other load-testing tools

JMeter is not the only choice and the section does not teach the others. Gatling (plans written as code in a Scala, Java or Kotlin DSL), k6 (scripts written in JavaScript) and Locust (users written as Python classes) are widely used alternatives with a code-first workflow; JMeter’s strengths are the GUI for building plans, the broad protocol coverage through its samplers and plugins and the large body of existing plans and knowledge. The choice is mostly about team skills and the protocols needed. JMeter also has programmatic routes (the 5.6 Java and Kotlin DSL and the third-party JMeter Java DSL) described near the end of the section.

What changed over time

The three books used for methodology were written for JMeter 2.11, 2.12 and 3.0. The architecture described above is the same, but the tooling around it changed.

Topic Books (2.x and 3.0) JMeter 5.6.3, and planned for 6.0

Mode names

"Non-GUI mode" (Pro ch. 3 and 5)

CLI mode.

Java

Java 7 or 8

Java 8 minimum for 5.6.x, 17 or later recommended; 6.0 is planned to need Java 17 or later (21 recommended).

WorkBench

Used to hold the recorder and non-test elements (Pro ch. 4)

Removed in 4.0; the recording template and the HTTP(S) Test Script Recorder replace it.

Scripting elements

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

BSF deprecated in 3.1 (still shipped, hidden from the menus), BeanShell discouraged; JSR223 with Groovy is the standard.

SOAP/XML-RPC sampler

Present (Cookbook ch. 4; PTwJ ch. 7)

Removed in 3.2; send SOAP with an HTTP Request and an XML body.

Monitor Results listener

Present (PTwJ ch. 5; Pro ch. 9)

Removed in 3.2.

Open workload

Not available in core; plugin thread groups and the Throughput Shaping Timer (Cookbook ch. 6)

Open Model Thread Group (experimental) since 5.5.

GraphQL

Not available

GraphQL HTTP Request since 5.4.

Java and Kotlin DSL

Not available

Experimental DSL in 5.6.

MongoDB elements

Present (Cookbook ch. 4; PTwJ ch. 7)

Deprecated; planned for removal in 6.0.

Summary

  • JMeter is a pure Java, protocol-level load generator. It is not a browser: no JavaScript execution, no rendering.

  • A test is a .jmx tree; each virtual user is one Java thread that executes it, blocking on every request.

  • Samplers produce SampleResult objects that post-processors, assertions and listeners consume in a fixed order; the results end up in a JTL file, the HTML dashboard or a time-series database.

  • Design and debug in GUI mode; run the load in CLI mode.

  • The load generator is part of the experiment: keep listeners, assertions and saved data small, and watch its CPU and heap.

References