DEV Community

Cover image for How to Find Your API’s Breaking Point (Before Your Users Do) - Capacity Testing with JMeter
Oleh Koren
Oleh Koren

Posted on • Edited on

How to Find Your API’s Breaking Point (Before Your Users Do) - Capacity Testing with JMeter

When you build an API service, it’s crucial to know how many users or requests it can handle before things start breaking. This is where capacity testing comes in.

What is Capacity Testing?

Capacity testing identifies the maximum load your system can handle before performance degrades or errors appear.

It helps detect bottlenecks and verify performance requirements under heavy load.

Capacity Testing an E-Commerce API with Apache JMeter

Let’s say we have a REST API for an online store. We want to see how many requests it can handle before it starts failing.

Step 1: Organize Thread Groups

Option 1: Separate Thread Groups per endpoint

Why? Because if one endpoint starts to degrade (high response time or errors), it can affect the throughput of other endpoints if tested together in the same group.

Separate Thread Groups allow:

  • Independent load for each endpoint
  • Clear identification of which endpoint is the bottleneck
  • Easier reporting and analysis

Option 2: Mixed workload in one Thread Group

In this case:

  • Multiple endpoints are executed together
  • Load is distributed based on user behavior percentages
  • The goal is to identify overall system capacity under real-world conditions

In this example, we use Option 1.

Step 2: Load Configuration

Number of Threads (Users)
Set per Thread Group based on expected usage (e.g., browsing endpoints typically require more users than checkout).

Ramp-Up Period
Defines how quickly users are added.
Short ramp-up creates spikes; longer ramp-up simulates gradual traffic growth (10 minutes).

Loop Count and Duration
Loop Count = Infinite, with a 10-minute test duration.

Startup Delay
Used for sequential Thread Groups to allow system recovery before applying the next load stage (the first Thread Group starts immediately).

Increase users step-by-step to determine the capacity limit.

Step 3: Add Configuration Elements

1. Add User-Defined Variables
Create a User-Defined Variables element to store values you’ll use across your test plan:

Why use variables?

  • Makes it easy to adjust load or URLs without editing each Thread Group
  • Supports parameterization for multiple environments (dev, staging, production)
  • Keeps the test plan organized and maintainable

Now pass the created variables into the Thread Groups (example):

2. Add HTTP Request Defaults
Add an HTTP Request Defaults element to set common request parameters, so you don’t have to repeat them in every HTTP Request sampler:

Step 4: Add HTTP Requests

Add an HTTP Request sampler to each Thread Group.
Set the API endpoint and HTTP method (GET, POST, etc.) accordingly.

Example:

Thread Group 1 → GET /products – browse products
Thread Group 2 → GET /products/{id} – view product details
Thread Group 3 → GET /products/search?q={searchPhrase} – search products by phrase
Thread Group 4 → GET /orders/{orderId} – order details
… and so on

Current Test Plan structure:

Step 5: Add Think Time

Think Time simulates real user pauses between actions.
In other words, it simulates the time a real user spends "thinking," reading, or interacting with a page before making the next request.

Why It Matters

Without think time:

  • JMeter sends requests back-to-back, which is not realistic
  • The load pattern may overwhelm the system compared to real users
  • Metrics like response time, throughput, and error rate can be misleading

With think time, the test more accurately reflects real user behavior, helping identify bottlenecks under realistic traffic conditions.

How to Implement in JMeter

Use Timers such as Constant, Uniform Random, or Gaussian Random.

Placement:

  • Place the Timer at the same hierarchical level as configuration elements in your Test Plan (above all Thread Groups).
  • When positioned here, the Timer applies globally to all Thread Groups.
  • This ensures consistent think time across all endpoints without repeating the Timer in each group

  • Constant Delay Offset = 2000 ms → every request waits at least 2 seconds before executing.
  • Random Delay Maximum = 4000 ms → adds a random delay between 0 and 4 seconds.

Each request will randomly wait somewhere between 2 and 6 seconds

Step 6: Add Listeners

Listeners in Apache JMeter are used to collect, display, and export test results. They help you analyze performance metrics during and after test execution.

1. Add Aggregate Report

Aggregate Report – summary metrics (avg, min, max, throughput, error %).
Useful for quick performance evaluation.

2. Add View Results Tree

View Results Tree – debugging only (inspect requests and responses).

3. Add Backend Listener

Backend Listener – send metrics to storage (InfluxDB) and analyze in Grafana for real-time dashboards and historical comparison.

This approach allows you to:

  • Monitor performance in real time
  • Store historical test data
  • Build dashboards for trend analysis
  • Compare multiple test runs

Using the Backend Listener with InfluxDB and Grafana provides a much more professional, production-ready analysis than basic JMeter listeners.

Current Test Plan:

Step 7: Run Test and Observe

Start with a low number of users and increase gradually.

Monitor server resources (CPU, memory, database connections) while testing.

Capacity is reached when:

When throughput stops increasing, response times spike, or errors appear, this indicates the capacity limit for that endpoint.

Step 8: Example Result

This example shows a 10-minute test with a 10-minute ramp-up to 100 users for the first endpoint:
GET /products

Since ramp-up equals test duration, users were added gradually throughout the entire test. Throughput increased steadily as concurrency grew.

However, 500 errors appeared and increased with higher load, indicating endpoint degradation before reaching a stable throughput plateau. As a result, the capacity point could not be clearly determined, and a defect should be created for further investigation.

This graph represents the response time metrics for the same 10-minute test.

At the beginning of the test, response time is higher (~1 second) due to warm-up and cache initialization.

After the initial minute, response time stabilizes around:

  • P50 ≈ 510 ms
  • P90 ≈ 540 ms
  • P95 ≈ 580 ms

Although latency remained relatively stable, the increasing error rate indicates system instability under higher concurrency.

In summary:

  • Errors were present from the early stage of the test and increased with higher load
  • Although response times remained relatively stable, the growing error rate indicates system instability
  • A clear capacity point could not be determined
  • Further investigation and defect creation are required before continuing capacity validation

Final Thoughts

Capacity testing should be:

  • Incremental
  • Endpoint-specific
  • Data-driven

Testing endpoints separately provides clearer insights and prevents one bottleneck from hiding another.

Only after stabilizing individual endpoints should you move to mixed workload testing for full system capacity validation.

Want to Go Further?

If you’d like to dive deeper into other performance testing types and learn how to build a complete testing stack using JMeter, InfluxDB, and Grafana for real-time monitoring and analysis, you can explore the full course here:

👉 Performance Testing Fundamentals: From Basics to Hands-On (Udemy)

Top comments (1)

Collapse
 
_01 profile image
Пилипюк Ольга

Дуже цікава та змістовна стаття, дякую!