JMeter for Load Testing Java APIs
Setting up JMeter to load test a Java REST API — the tests I actually run and what I look for in the results.
Load testing is the kind of thing that gets skipped when the deadline is close and then becomes very urgently necessary at about 11pm on the night before a big release when someone asks “but what happens when 500 users hit this at the same time?”
JMeter is my go-to for this. It’s free, it’s mature, it’s overkill for most things, and it has a GUI that looks like it was designed in 2003 — which it was, and somehow that’s become comforting.
What I’m usually testing for
Before opening JMeter, I have a question I’m trying to answer. Usually one of:
- Does the application survive expected peak load?
- Where does it start to degrade — what’s the breaking point?
- Is there a memory leak or connection pool exhaustion under sustained load?
The test setup differs for each.
Basic HTTP request test
Open JMeter, create a Thread Group, add an HTTP Request sampler.
The Thread Group controls the load:
- Number of Threads — concurrent users
- Ramp-Up Period — how long to take to reach full concurrency (don’t jump to 500 users at once)
- Loop Count — how many times each thread runs the test
For the HTTP Request:
- Protocol:
httporhttps - Server:
myapp.example.com - Port:
443 - Method:
GET/POST - Path:
/api/payment
Add a View Results Tree listener (for debugging) and an Aggregate Report listener (for the actual metrics).
Running from command line (non-GUI mode)
The GUI is useful for building tests. For actual load runs, use non-GUI mode — the GUI itself consumes resources and skews results:
1
2
3
4
5
6
$ jmeter -n -t my_test_plan.jmx -l results.jtl -e -o ./report/
# -n non-GUI mode
# -t test plan file
# -l log results to this file
# -e -o generate HTML report into ./report/
The HTML report is actually quite good — response time percentiles, throughput over time, error rates, all in one page.
Parameterising requests with CSV data
For payment testing, you need different amounts, currencies, account numbers. Hardcoding one value means you’re effectively caching and not testing real behaviour.
Create a payment_data.csv:
1
2
3
4
accountId,amount,currency
ACC001,10050,ZAR
ACC002,25000,USD
ACC003,5000,ZAR
In JMeter, add a CSV Data Set Config element pointing at the file. Reference the values in your HTTP Request with ${accountId}, ${amount}, ${currency}.
What to look for in the results
Response time percentiles — p50, p95, p99. The average is almost useless in isolation. The p99 tells you what the worst 1% of users experience. If your p99 is 8 seconds and your p50 is 200ms, something is occasionally very wrong.
Throughput — requests per second your system can sustain. If it starts dropping as you add load, you’ve found the ceiling.
Error rate — should be 0% at normal load. If you’re seeing errors at 50 concurrent users, that’s not a load problem, that’s a bug.
Thread pool/connection pool exhaustion — watch your application logs during the test. HikariPool-1 - Connection is not available, request timed out after 30000ms is a sign that your DB connection pool is too small for the load.
Testing for memory leaks
Run a sustained test for 30–60 minutes at a constant thread count. Watch the JVM heap with:
1
$ jstat -gc <pid> 5000
Print GC stats every 5 seconds. If the old generation (OU column) keeps growing and never comes back down, you have a memory leak. If it grows during the test but drops after you stop sending traffic, that’s normal heap behaviour.
Bearer token auth
Most modern APIs require auth. Add a HTTP Header Manager to the Thread Group with:
- Name:
Authorization - Value:
Bearer ${access_token}
If you need to generate the token dynamically, add a separate HTTP Request for the login/token endpoint before your main requests, then use a JSON Extractor to pull the token from the response:
- Variable Name:
access_token - JSON Path Expression:
$.access_token
The ${access_token} variable is then available in subsequent requests.