JMeter
A practical reference for Apache JMeter — the components you'll wire together, the assertions you'll write, and the CLI flags you'll reach for in CI.
Thread Groups
The Thread Group sets concurrency, ramp-up, and iteration count. Every test plan starts with at least one.
Standard Thread Group
Number of Threads (users): 100
Ramp-Up Period (seconds): 60 # 100 users started over 60s
Loop Count: 10 # each user runs the test 10 times
# — or check "Forever" and use a Duration timer
Duration (seconds): 300 # cap the run at 5 minutes
Startup delay (seconds): 0
Concurrent users at any moment: Threads × (1 - elapsed_time / ramp_up) while ramping, then full Threads.
Stepping Thread Group (jp@gc plugin)
Gradual load increase, useful for finding breaking points.
Threads: 1000
Initial delay: 0s
Start: 0 threads
Increment: 50 threads every 30s, hold 5s between
Hold: 300s at peak
Shutdown: 50 threads every 10s
Ultimate Thread Group (jp@gc plugin)
Multi-row schedule for complex load patterns.
Row 1: 50 threads, 30s startup, 60s hold, 30s shutdown
Row 2: 50 threads, 60s startup, 120s hold, 30s shutdown
Combines into a layered ramp profile.
setUp and tearDown Thread Groups
Run before/after the main test plan — ideal for one-time DB seeding or cleanup. Threads here don't count toward main load.
Quick concurrency math
Concurrent users = Throughput (req/sec) × Avg Response Time (sec) / Pacing
Throughput target = Users × Iterations / Total Run Time
Samplers
Samplers issue the actual requests.
HTTP Request
Method: GET
Server: api.example.com
Port: 443
Protocol: https
Path: /v2/users
Body data: {"name":"Ada"} # for POST/PUT
Use ${variable} syntax to interpolate from CSV / extractors:
/v2/users/${user_id}
JDBC Request
For database load tests, pair with JDBC Connection Configuration.
SELECT id, status FROM orders
WHERE customer_id = ${customer_id}
AND created_at > NOW() - INTERVAL '1 day';Query Type: Select Statement
Variable Names: order_id, order_status
Result Variable: result
JSR223 Sampler (Groovy)
For custom logic that doesn't fit a simple HTTP/JDBC sampler.
def random = new Random()
def value = random.nextInt(1000)
vars.put("random_id", value.toString())
log.info("Generated id: ${value}")
SampleResult.setResponseData("id=${value}", "UTF-8")
SampleResult.setSuccessful(true)Prefer Groovy over BeanShell — Groovy compiles once, BeanShell interprets on every iteration.
Debug Sampler
Captures the current state of all variables and properties — useful while authoring tests, disable in real load runs.
Configuration Elements
Defaults applied to all child samplers in the same scope.
HTTP Request Defaults
Server: api.example.com
Port: 443
Protocol: https
Path: /v2
Connect Timeout: 5000ms
Response Timeout: 30000ms
HTTP Header Manager
Content-Type: application/json
Accept: application/json
Authorization: Bearer ${auth_token}
X-Request-ID: ${__UUID()}
HTTP Cookie Manager
Drop in once per Thread Group. Handles Set-Cookie automatically across requests, including session cookies.
CSV Data Set Config
Filename: users.csv
Variable Names: username,password,email
Recycle on EOF: True
Stop thread on EOF: False
Sharing mode: All threads
users.csv:
ada,secret1,ada@example.com
bob,secret2,bob@example.com
Reference in samplers: ${username}.
User Defined Variables
base_url https://api.example.com
api_version v2
default_timeout 5000
Set once at plan or thread-group level — useful for environment switching.
JDBC Connection Configuration
Variable Name: ordersDB
URL: jdbc:postgresql://db.internal:5432/orders
Driver class: org.postgresql.Driver
Username: ${db_user}
Password: ${db_pass}
Pool: 10 connections
Assertions
Validate response correctness, not just availability.
Response Assertion
Apply to: Main sample only
Field to test: Response Body (or Response Code, Headers, URL)
Pattern matching rules: Contains (or Matches, Equals, Substring)
Pattern: "status":\s*"ok"
JSON Assertion
JSON Path: $.user.email
Expected Value: ada@example.com
Match as regex: false
Invert assertion: false
Duration Assertion
Duration in milliseconds: 2000
Fails samplers that take longer than 2 seconds.
Size Assertion
Size in bytes: 1048576
Type of comparison: less than
XPath Assertion
For XML/HTML responses.
//user[@id='${user_id}']/emailPre/Post Processors
JSR223 PreProcessor (Groovy)
Generate dynamic data before the sampler runs.
import java.security.MessageDigest
def ts = System.currentTimeMillis().toString()
def secret = vars.get("api_secret")
def payload = ts + vars.get("user_id")
def digest = MessageDigest.getInstance("SHA-256")
def hash = digest.digest((payload + secret).bytes).encodeHex().toString()
vars.put("timestamp", ts)
vars.put("signature", hash)JSON Extractor
Capture values from JSON responses for use in subsequent requests.
Names of created variables: auth_token
JSON Path expressions: $.access_token
Match No.: 1 # 0 = random, -1 = all matches
Default Values: NOT_FOUND
Then ${auth_token} in later samplers.
Regular Expression Extractor
For correlating non-JSON responses (HTML, plain text).
Names of created variables: csrf
Regular Expression: name="csrf_token" value="([^"]+)"
Template: $1$
Match No.: 1
Default Value: NOT_FOUND
CSS/JQuery Extractor
Reference Name: page_title
CSS Selector: h1.page-title
Attribute: (leave blank for text)
Match No.: 1
XPath Extractor
Reference Name: order_id
XPath Query: //order[1]/@id
Listeners
Disable all listeners during real load tests — they consume memory and skew results. Run with the -l CLI flag instead and analyse offline.
View Results Tree
Per-request request/response detail. Author and debug only.
Summary Report
Aggregate stats per sampler — count, average, min/max, error %, throughput, KB/s. The cheapest listener for live runs.
Aggregate Report
Adds percentiles (50 / 90 / 95 / 99) — what most performance reviews actually care about.
Backend Listener (InfluxDB / Graphite)
Stream metrics to a time-series DB during the run for live Grafana dashboards.
Backend Listener implementation: InfluxdbBackendListenerClient
influxdbUrl: http://influx.internal:8086/write?db=jmeter
application: my-api
measurement: jmeter
Simple Data Writer
Write raw samples to a .jtl file — configurable to JSON, CSV, or XML.
Timers
Insert pauses to model real user think-time and avoid hammering the target.
Constant Timer
Thread Delay (ms): 1000
Gaussian Random Timer
Deviation (ms): 500
Constant Delay: 1000
Delays distribute around 1000ms ± 500.
Uniform Random Timer
Random Delay Maximum (ms): 2000
Constant Delay Offset: 500
Delay is 500 + rand(0, 2000).
Constant Throughput Timer
Caps a thread group to a target rate regardless of response time.
Target throughput (samples/min): 600 # 10 req/sec
Calculate Throughput based on: all active threads in current thread group
CLI Mode (Non-GUI)
GUI mode is for authoring only — always run real load tests headless.
Basic run
jmeter -n -t test.jmx -l results.jtl-nnon-GUI mode-ttest plan-lresults file (.jtl)
Generate the dashboard report
jmeter -n -t test.jmx -l results.jtl -e -o ./report-egenerate dashboard report at end-ooutput folder (must not exist)
Override properties from CLI
jmeter -n -t test.jmx \
-l results.jtl \
-Jthreads=200 \
-Jramp_up=120 \
-Jduration=600 \
-Jbase_url=https://staging.example.comIn test.jmx reference these as ${__P(threads,50)} (with default).
Generate report from existing JTL
jmeter -g results.jtl -o ./reportDistributed (master/worker) run
jmeter -n -t test.jmx -l results.jtl \
-Rworker1.example.com,worker2.example.com \
-X-Rcomma-separated worker hosts-Xshut down workers after run
Correlation & Dynamic Data
Most real APIs require capturing dynamic values (session, CSRF, IDs) from one response and feeding them into the next.
Capture and reuse a token
-
Add JSON Extractor under the login request:
Variable: auth_token JSON Path: $.access_token -
In the next sampler's headers (HTTP Header Manager):
Authorization: Bearer ${auth_token}
CSRF token correlation
Form pages typically inline a CSRF token in HTML. Extract with Regular Expression Extractor:
Variable: csrf
Regex: name="csrf_token" value="([^"]+)"
Template: $1$
Submit with the form post body:
csrf_token=${csrf}&username=ada&password=secret
Built-in functions
${__counter(TRUE,)} per-thread counter (TRUE) or global (FALSE)
${__time(yyyy-MM-dd,)} formatted current time
${__time(/1000,)} Unix timestamp seconds
${__UUID()} v4 UUID
${__Random(1,1000)} random int between bounds
${__RandomString(10,abc123)} random string from char set
${__P(propName,default)} read JVM property (overridable via -J)
${__threadNum()} current thread index
${__iterationNum()} current iteration index
${__groovy(System.currentTimeMillis())} inline Groovy
${__BeanShell(some.code())} legacy, prefer __groovy
Parameterise via CSV (per-iteration data)
csv: users.csv → variables: username, password
Each thread reads one row per iteration; the request body uses ${username} / ${password} and runs through the CSV.