Performance Testing
Intermediate 13 min read Performance Testing

Apache JMeter

Apache JMeter is a Java-based load testing tool that models workloads as test plans: thread groups, samplers, timers, assertions and listeners stored in a .jmx file. Master headless runs, properties and the HTML dashboard.

JMeterJMXthread groupsamplerlistenernon-GUI mode
Introduction

What is this?

Apache JMeter is a Java-based, GUI-driven load testing tool that models workloads as test plans: thread groups, samplers, listeners, timers, assertions and logic controllers wired together in a .jmx file. It supports HTTP, HTTPS, JDBC, JMS, FTP, TCP and gRPC out of the box and is the de facto standard in enterprise and API load testing. Because a .jmx plan is XML, it can be version-controlled, reviewed and run headlessly from CI.

Motivation

Why do you need it?

  • Protocol breadth: HTTP/HTTPS, JDBC, JMS, FTP, TCP and gRPC — one tool covers API and service testing.
  • A mature GUI for recording and debugging plans, plus an XML format you can diff and review in code review.
  • Runs headlessly (jmeter -n) so the same plan executes in CI with no display server.
  • Distributed mode lets you scale load across many remote agent machines.
  • A large plugin ecosystem (Custom Thread Groups, Prometheus backend listener, InfluxDB writers) extends it.
Think about it

Mental model

Imagine

A factory production line

Think of

A JMeter test plan

Threads are the workers, samplers are the machines each worker operates, timers pace the work, assertions are the quality inspectors, and listeners log the output. You design the line, then switch it on and watch the gauges.

Mechanics

How it works

  • A Thread Group defines the number of virtual users (threads), the ramp-up period and the loop count — this is the concurrency model.
  • Samplers (HTTP Request, JDBC Request, JMS Publisher, ...) define the actual operation each virtual user performs.
  • Timers add think time and pacing so virtual users behave like real users instead of a tight loop.
  • Assertions validate each response (status code, response body regex) so failures are counted, not just measured.
  • Listeners (Aggregate Report, Summary Report, Graph Results) collect and render metrics during and after the run.
  • jmeter -n -t plan.jmx -l results.jtl -e -o report/ executes the plan headlessly and renders an HTML dashboard.

Anatomy of a JMeter test plan and how one request flows through it.

Test Plan
Thread Group
Sampler
Timer
Assertion
Listener
Reference

Key commands

jmeter -n -t plan.jmx -l results.jtl -e -o html-report

Run the plan headlessly, write samples to results.jtl and generate an HTML dashboard.

summary = 2000 in 00:01:30 = 22.2/s Avg: 45 Min: 12 Max: 310 Err: 0 (0.00%)
jmeter -n -t plan.jmx -l results.jtl -j run.log

Run with a separate log file so run diagnostics never mix with sample results.

jmeter -n -t plan.jmx -R agent1,agent2 -l results.jtl

Distribute the load across remote agents for higher total concurrency.

Starting remote engines: agent1 agent2
jmeter -n -t plan.jmx -Jusers=100 -Jramp=10 -l results.jtl

Override properties from the command line without editing the plan (read via ${__P(users,20)}).

jmeter -t plan.jmx

Open the plan in the GUI for authoring and interactive debugging.

jmeter --version

Print the JMeter version and the Java runtime it runs on.

Hands-on

Examples

A minimal HTTP test plan in JMX XML

A minimal HTTP test plan in JMX XML
<?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="API Smoke Plan">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments"/>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Users">
<intProp name="ThreadGroup.num_threads">20</intProp>
<intProp name="ThreadGroup.ramp_time">5</intProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<stringProp name="TestPlan.comments">20 users ramping over 5s</stringProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /v1/items">
<stringProp name="HTTPSampler.domain">api.example.com</stringProp>
<stringProp name="HTTPSampler.port">443</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.path">/v1/items</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
<hashTree/>
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Expect 200">
<collectionProp name="Asserion.test_strings">
<stringProp name="49586">200</stringProp>
</collectionProp>
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
<stringProp name="Assertion.assume_success">false</stringProp>
<intProp name="Assertion.test_type">8</intProp>
</ResponseAssertion>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

A Test Plan wraps a Thread Group of 20 threads that ramp over 5 seconds; each iteration sends one HTTPS GET to /v1/items. A Response Assertion flags any sample whose response code is not 200, and the JTL listener (added with -l on the CLI) records every sample. The plan is plain XML, so it diffs cleanly in code review.

Real world

In production

  • A nightly JMeter suite reuses the same .jmx plans to regression-test latency budgets on the API.
  • Thread counts and hosts are parameterized with JMeter properties so one plan runs across environments.
  • Results ship to Grafana via the Backend Listener (InfluxDB) so load tests appear next to production metrics.
  • JMeter runs in containers as a Kubernetes Job so capacity tests scale without a dedicated load lab.
Guidelines

Best practices

  • Never build a plan purely by recording; add assertions, timers and correlation so it tolerates dynamic values.
  • Use JMeter properties (${__P(users,10)}) instead of hard-coding thread counts and hosts.
  • Avoid heavy listeners in large runs — write to JTL with Simple Data Writer and render the dashboard afterwards.
  • Remember each thread is a JVM thread: heap size and file descriptors are part of your load budget.
  • Version-control the .jmx plus a readme describing the workload, because XML plans are hard to review by eye.
Watch out

Common mistakes

  • Placing a Listener inside a Thread Group, which aggregates per-thread instead of globally.
  • Using the GUI for large runs — it consumes heap and skews results; always run headless for real tests.
  • Sending no think time, producing throughput no real user could generate and unrealistic server pressure.
  • Treating JTL results from different thread groups as comparable without recording ramp-up and loop counts.
Root cause analysis

Common issues

Error
Error in rconfigure() method: rmi problem / couldn't connect to remote engine
Root cause

Distributed mode uses RMI, which defaults to localhost; IPs or ports do not match.

Fix

Start agents with -Djava.rmi.server.hostname=<agent-ip> and open the RMI (1099) and agent ports.

Error
OutOfMemoryError: Java heap space during a large run
Root cause

Heavy listeners or too many threads for the configured JVM heap.

Fix

Drop GUI listeners, stream results to JTL, and tune -Xmx / -Xms in jmeter.sh (jmeter.bat).

Error
Response code: 000 / connection reset
Root cause

The server closed connections under load (keep-alive timeouts, pool exhaustion).

Fix

Check server connection limits, and set HTTPSampler.keepalive=true plus a realistic connection timeout.

When things break

Debugging tips

  • Enable JMeter logging with -j run.log and set jmeter.log.level=DEBUG to trace sampler failures.
  • Use the Assertion Results listener while authoring to see exactly which assertion failed and why.
  • Check the JTL response-time delta across thread groups — a jump at high thread counts points to server saturation.
  • Diff the .jmx after GUI edits to review accidental changes before committing.
Interviews

Interview checkpoint

Practice

Hands-on lab

Run a JMeter Plan Headlessly
30 min · intermediate
In progress
Objectives
  • Author a .jmx plan with a thread group, sampler and assertion
  • Parameterize users and ramp with JMeter properties
  • Run the plan in non-GUI mode from the CLI
  • Generate and read the HTML dashboard

Run a JMeter Plan Headlessly

30 min · intermediate

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Which JMeter component defines the number of concurrent virtual users?

2

A JMeter test plan is stored as XML (.jmx), which makes it suitable for version control and CI.

3

You need JMeter to fail a build if more than 1% of responses return a status other than 200. What should you add?

Quick reference

Cheat sheet

JMeter essentials
Thread Groupnum_threads, ramp_time, loops
HTTPSamplerProxyHTTP request sampler
TimerThink time / pacing
AssertionValidate status or body
ListenerCollect and render metrics
jmeter -n -t plan.jmx -l results.jtl -e -o outHeadless run + dashboard
Go deeper

Official documentation