Number of Threads (Users) in JMeter: The Complete Advanced Guide
Learn what the thread count really means, how JMeter creates and runs threads, how to calculate the right number from throughput and response time, how to size load generators, and how to avoid the mistakes that make performance test results meaningless.
Imagine a bank with ten counters. On a quiet Tuesday morning, three customers walk in, and everything looks smooth. On the first day of the month, three hundred customers arrive in an hour, and suddenly the queue reaches the street. The bank did not change. Only the number of people arriving at the same time changed. A performance test with JMeter asks the same question about your application: what happens when many people arrive together?
In JMeter, the dial that controls “how many people” is a single field called Number of Threads (users). It looks harmless, just one box with a number. But this one number decides how much pressure your application feels, how much memory and CPU your test machine burns, how believable your results are, and whether your final report tells the truth or tells a comfortable story. This guide explains that one number in depth, from the very first idea to the advanced topics that experienced performance engineers worry about.
1What “Number of Threads (Users)” Really Means
One field, one number, and a lot of hidden meaning.
The simple definition
A thread in JMeter is one independent worker that runs your test plan from top to bottom. When you type 100 in the Number of Threads field of a Thread Group, JMeter starts 100 such workers. Each worker takes the list of steps you designed, such as open the home page, log in, search for a product, add it to the cart, and pay, and performs those steps again and again. Because JMeter calls each worker a “user”, the field is labelled “Number of Threads (users)”.
Each worker is separate from the others. It keeps its own variables, its own cookies, its own login session, and its own position in the test plan. If one worker is waiting for a slow response, the others carry on. This independence is exactly what makes the test look like many different people using the system at the same moment.
Think of a thread as a mystery shopper. You hire 100 mystery shoppers and give each one the same shopping list. They all walk into the store, each at their own pace, and each one fills a basket. The store manager (your application) must serve all of them. The number of mystery shoppers you hire is the Number of Threads.
Why this one number matters so much
The thread count is the main control for concurrency, which means how many activities are in progress at the same moment. Too few threads and your test is too gentle: the application looks healthy, and you ship a problem into production. Too many threads and you may overload your own test machine, so the slowness you measure belongs to JMeter, not to the application. In both cases the report looks professional, but the conclusion is wrong.
Many beginners believe that 100 threads means exactly 100 requests per second. It does not. Threads measure how many users are active, not how many requests are made each second. The number of requests per second depends on how long each request takes and how long each user pauses. You will see the exact formula in Chapter 7.
What the thread count does not control
The thread count does not directly set the speed of the test, the total number of requests, or the duration. Those are decided by the Ramp-up Period, the Loop Count, the scheduler, timers, and the response time of the system under test. The thread count only sets the maximum number of workers that can be active together. Understanding this separation is the first step toward designing correct tests.
2Threads, Virtual Users, Real Users and Sessions
Four words that people use as if they mean the same thing, but they do not.
Performance conversations become confusing because teams mix up words. A business owner says “we expect 5,000 users”. A tester configures 5,000 threads. The application then receives a load that has little to do with the business expectation. Before choosing a number, you need a shared vocabulary.
Thread (Virtual User)
One JMeter worker that repeats the scripted journey. It exists only inside the load generator and is not a person.
Registered User
Anyone who has an account. A system may have two million of them while only a few thousand are online at peak time.
Active Session
A person who currently has the application open and recently did something. This is the group closest to your thread count.
Concurrent Request
A request that is being processed by the server at this very instant. Usually far smaller than the number of active sessions.
Why the difference is important
A real person reads, thinks, scrolls, and drinks tea between clicks. A JMeter thread, unless you tell it otherwise, never pauses. It sends the next request the instant the previous response arrives. A single thread without any pause can create as much load as ten or twenty real people, depending on how fast the application responds. So “5,000 users” and “5,000 threads” are not the same thing unless you add realistic pauses between steps.
A real shopper walks around a store for twenty minutes and visits the checkout once. A thread with no pause is like a shopper who sprints to the checkout, pays, runs back to the shelf, and pays again, over and over, without breathing. One sprinter puts as much pressure on the cashier as many calm shoppers.
Always write down which definition your team means when it says “users”. Ask: do we mean registered users, users online in a peak hour, or users clicking at the same second? Agree on the definition before you touch JMeter. This one conversation prevents most disagreements later.
The bridge between business numbers and thread numbers
The bridge is a pair of ideas: think time and throughput. If you reproduce realistic think time, then the number of active sessions maps to the number of threads fairly closely. If you remove think time, you should instead target a request rate and calculate how many threads are needed to reach it. Both approaches are valid, but you must pick one on purpose. Chapters 7, 8 and 10 explain both approaches in detail.
3Anatomy of the Thread Group
The Number of Threads field lives inside a Thread Group, and its neighbours change its meaning.
A Thread Group is the container that holds your test steps and tells JMeter how to run them. Every test plan needs at least one. The Number of Threads field is only one of several settings, and the settings work as a team. Changing one often changes the effect of the others.
Number of Threads (users)
How many workers will be created in total for this Thread Group. This is the maximum concurrency the group can reach.
Ramp-up Period (seconds)
The time JMeter takes to start all the threads. The threads are spread evenly across this period.
Loop Count
How many times each thread repeats the whole scenario. It can also be set to run forever, until the test is stopped.
Same User on Each Iteration
Decides whether a thread keeps its cookies and session between loops, like a returning visitor, or starts fresh like a new visitor.
Delay Thread Creation Until Needed
Creates each thread only when its start time arrives, instead of creating all of them at the beginning. This saves memory in long ramp-ups.
Specify Thread Lifetime
Lets you set a duration and a startup delay, so the group runs for a fixed time instead of a fixed number of loops.
The action after a sampler error
Beside these fields there is a choice that tells each thread what to do when a request fails. The thread can continue with the next step, start the next loop, stop itself, stop the whole test gracefully, or stop the whole test immediately. This choice quietly affects your thread count in practice. If failing threads stop themselves, then during a failure storm your 500 threads may shrink to 200 without any warning, and your test no longer applies the load you planned. For capacity tests, most teams choose to continue or start the next loop, so the load stays constant and the failures show up clearly in the report.
If threads stop after an error, a broken application can make your own load disappear. The system looks like it “recovered” only because fewer threads were left to hurt it. Always check the active threads graph during the run to confirm that the load stayed the same.
More than one Thread Group
A test plan may contain several Thread Groups, each with its own thread count. This is useful when different user types behave differently. For example, 800 threads may browse the catalogue, 150 may search, and 50 may check out. The total concurrency is the sum of all groups. When people say “the test used 1,000 users”, they usually mean this sum. Remember to report the number per group as well, because a mix of behaviours is more important than the total.
| Thread Group | Threads | Behaviour | Share of Load |
|---|---|---|---|
| Browsers | 800 | Home page, category pages, product pages | 80 percent |
| Searchers | 150 | Search queries and filters | 15 percent |
| Buyers | 50 | Login, cart, payment | 5 percent |
| Total | 1,000 | Mixed workload | 100 percent |
4How JMeter Runs Threads Internally
A look under the hood explains why thread counts have real physical limits.
JMeter is a Java program. Each JMeter thread is a real Java thread running inside one Java Virtual Machine, usually called the JVM. When the test starts, the engine reads your test plan, makes a private copy of the plan elements for every thread, and starts the threads according to the ramp-up schedule. This copy is the reason each thread can hold different variables and different cookies without interfering with the others.
flowchart TD
A["Test starts"] --> B["Engine reads the Thread Group settings"]
B --> C["Engine makes a private copy of the plan for each thread"]
C --> D["Threads start one by one during the ramp-up"]
D --> E["Each thread runs samplers in order"]
E --> F["Timers add pauses between steps"]
F --> G{"More loops or time left?"}
G -->|"Yes"| E
G -->|"No"| H["Thread ends and frees its memory"]
H --> I["Test ends when all threads finish"]
The thread life cycle
Creation
The thread object is created with its own copy of the test elements and its own variable store. This costs memory, and the cost grows with the size of your test plan.
Start
When its scheduled moment arrives, the thread begins running. From here it works alone, without waiting for any other thread.
Execution loop
The thread sends a request, waits for the full response, processes assertions and extractors, waits for any timer delay, and moves on to the next step.
Shutdown
When the loops finish, the duration expires, or a stop command arrives, the thread exits. Graceful stop lets running requests finish. Immediate stop interrupts them.
Blocking behaviour: why one thread equals one request at a time
A JMeter thread is synchronous. This means that while it waits for a response, it does nothing else. It cannot send a second request in the meantime. So a single thread can have at most one request in flight at any moment. This simple fact is the foundation for the maths in the next chapters: the maximum number of requests in progress at the same time is never larger than the number of threads.
A thread is like a waiter who can carry only one order at a time. He takes an order to the kitchen and stands there until the food is ready. If the kitchen is slow, he stands for a long time and the restaurant serves fewer tables per hour. To serve more tables at once you need more waiters, which means more threads.
What each thread costs your test machine
A thread consumes memory for its stack and for the variables, response data and sampler results it holds. It also uses CPU time for building requests, encrypting and decrypting secure traffic, parsing responses, and running scripts. A plain page request is cheap. A request that uses heavy scripting, large response bodies, or many regular expressions is expensive. Two test plans with the same thread count can therefore need very different hardware. This is why Chapter 13 is devoted to sizing the machine that generates the load.
5Ramp-up Period: How Fast the Threads Arrive
The thread count says how many. The ramp-up says how quickly.
The Ramp-up Period is the number of seconds JMeter takes to start all of the threads. The start times are spread evenly. If you have 100 threads and a ramp-up of 50 seconds, JMeter starts one new thread every half second. If you set a ramp-up of zero, all 100 threads start almost together, and your application receives a sudden wall of traffic.
flowchart LR
A["Ramp-up phase: threads start gradually"] --> B["Steady state: all threads active, measure here"]
B --> C["Ramp-down phase: threads finish and exit"]
Why a gradual start is healthier
Real traffic rarely appears in one second. Even a marketing email arrives across several minutes. A slow ramp-up lets the application warm up: caches fill, connection pools open their connections, the runtime compiles hot code paths, and auto-scaling has time to react. If you slam the system with all users at once, you test a “cold start shock”, which is a valid scenario, but it is a different test, usually called a spike test. Mixing the two ideas gives results that nobody can interpret.
| Threads | Ramp-up | New thread every | What it simulates |
|---|---|---|---|
| 100 | 0 seconds | All at once | Spike or flash crowd |
| 100 | 50 seconds | 0.5 seconds | Brisk but fair arrival |
| 100 | 300 seconds | 3 seconds | Gentle morning log-in wave |
| 1,000 | 600 seconds | 0.6 seconds | Realistic peak-hour build-up |
The measurement window
Results collected during the ramp-up are not stable, because the load is changing every second. For honest numbers, you want a steady state: a period where all threads are active and the load is flat. A good test plan has a ramp-up, then a hold period long enough to take measurements, then a ramp-down. Many teams exclude the ramp phases when they calculate averages and percentiles.
Make the hold period at least two to three times longer than the ramp-up, and long enough to collect a statistically useful number of samples, often ten to thirty minutes for normal load tests. A ramp-up that takes longer than the hold period means you never really test the full load.
A subtle detail: the first iteration
Threads that start early are already on their second or third loop while the last threads are still starting. That is normal. But it means that during the ramp-up the mix of requests is skewed toward the early part of your scenario, because new threads begin at the first step while old threads are further along. Over a long steady state, this skew disappears.
6Loop Count, Duration and the Scheduler
Threads need a reason to stop. These settings decide when.
A thread keeps working until something tells it to stop. There are two main ways to define that moment. The first is a count of loops: each thread repeats the scenario a fixed number of times and then leaves. The second is a time limit: the thread keeps looping until the clock runs out. These two styles lead to very different tests, even with the same thread count.
Loop-based tests
In a loop-based test, the total work is predictable. With 100 threads and 10 loops, the test sends the scenario 1,000 times in total. The duration, however, is not predictable. If the application is fast, the test ends quickly. If it is slow, the test takes longer, and the load on the system gradually decreases as fast threads finish early and slow threads continue alone. This is a useful style for functional performance checks and for tests where you need an exact number of transactions, such as creating exactly 5,000 orders.
Time-based tests
In a time-based test, you enable the thread lifetime option, set a duration, and set the loop count to run forever. Now the duration is predictable, and the number of transactions is not. Every thread stays busy until the end, so the concurrency stays flat during the hold period. This is the preferred style for load, stress and endurance tests, because the shape of the load is easy to explain: “500 users for 30 minutes”.
| Style | You fix | You discover | Best for |
|---|---|---|---|
| Loop count | Number of iterations per thread | How long the test takes | Data creation, fixed-volume checks |
| Duration | Test length in seconds | How many transactions happen | Load, stress, soak tests |
| Both | Loops and a time cap | Whichever limit arrives first | Safety against runaway tests |
Setting a duration but leaving a small fixed loop count. The threads finish their loops in a couple of minutes and exit, long before the duration ends. The test then silently runs with fewer and fewer active threads. When you want a time-based test, set the loop count to infinite and let the duration do the stopping.
Startup delay
The startup delay setting postpones the start of a Thread Group. It is helpful when you have several groups that should begin at different moments. For example, a background batch job may start five minutes before the user traffic so that the system is already warm and busy when the users arrive. It is also a simple way to stack different groups in time without extra plugins.
A loop count is like telling runners to complete ten laps, no matter how long it takes. A duration is like telling them to keep running for thirty minutes, no matter how many laps they finish. Both are fair, but they answer different questions: how fast can we finish the work, versus how well do we hold up over time.
Why this matters for the thread count
The effective number of active threads at any second equals the threads that have started and not yet finished. In loop-based tests this number falls toward the end. In time-based tests it holds steady. If your report says “500 users” but the average active thread count was 380, then your claim is off by almost a quarter. Always confirm the active thread count from the live graphs or from a listener that records it, not just from the number typed into the field.
7Concurrency Versus Throughput: Little’s Law
The one formula every performance engineer should know by heart.
Two words come up again and again in performance work. Concurrency is how many things are happening at once. Throughput is how many things are completed per unit of time, such as requests per second. They are connected by a beautiful and simple rule named after the mathematician John Little. It says that, in a stable system, the average number of items inside the system equals the arrival rate multiplied by the average time each item spends inside.
Applying the rule to JMeter
For a JMeter test where every thread sends one request at a time, the number of threads in action equals the throughput multiplied by the time each cycle takes. A cycle is the response time of a request plus any pause the thread takes before the next one. Written in words: threads needed equals the target requests per second multiplied by the sum of response time and think time, measured in seconds.
Picture a water pipe. The amount of water inside the pipe at any moment depends on how fast it flows and how long the pipe is. A longer pipe holds more water at the same flow. In the same way, slower responses hold more users inside the system at the same request rate. Threads are the water inside the pipe.
Worked examples
| Target rate | Response time | Think time | Cycle time | Threads needed |
|---|---|---|---|---|
| 100 requests per second | 0.5 seconds | 0 seconds | 0.5 seconds | 50 |
| 100 requests per second | 2 seconds | 0 seconds | 2 seconds | 200 |
| 100 requests per second | 0.5 seconds | 5 seconds | 5.5 seconds | 550 |
| 20 requests per second | 1 second | 9 seconds | 10 seconds | 200 |
Look at the first two rows. The target rate is the same, but when the response time becomes four times slower, you need four times more threads to keep that rate. This is the reason a degrading application can fool you. If you keep the thread count fixed while the server slows down, the throughput drops, because each thread now finishes fewer cycles per second. The graph then shows a falling request rate, and some people mistakenly read this as “the application handled less traffic” when in fact the application caused the drop.
The reverse direction: predicting throughput from threads
You can also turn the rule around. Throughput equals the number of threads divided by the cycle time. With 200 threads and a cycle of 4 seconds, the system sees about 50 requests per second. If the response time grows from 1 second to 3 seconds while think time stays at 3 seconds, the cycle grows from 4 to 6 seconds and the throughput falls from 50 to about 33 per second. The load generator is the same, the threads are the same, but the pressure on the server changed because the server itself became slower.
When you plan a test, calculate the thread count with this rule first, then add a safety margin of ten to twenty percent. Extra threads cost little, and they allow the generator to keep the planned request rate even if the server slows down during the test.
flowchart LR
A["Target throughput"] --> D["Threads needed"]
B["Response time"] --> D
C["Think time and pacing"] --> D
D --> E["Concurrency on the server"]
8Think Time and Pacing: Making Threads Behave Like People
Without pauses, a thread is a machine. With pauses, it becomes a believable user.
What think time is
Think time is the pause between two steps of a user journey: the time a person takes to read a page, choose an option, or type a form. In JMeter you create this pause with timers. A fixed timer waits for the same duration each time. A uniform random timer waits for a random duration between a minimum and a maximum. A Gaussian random timer produces most waits near an average, with a few shorter and a few longer waits, which looks natural. Random timers are usually better because real people are not robots with a stopwatch.
Constant Timer
Waits exactly the same number of milliseconds every time. Simple, but it makes all threads march in step and can create artificial waves of traffic.
Uniform Random Timer
Adds a fixed base wait plus a random extra amount. Good all-round choice for realistic spread between users.
Gaussian Random Timer
Most waits cluster around an average with natural variation, which fits how real readers behave on a page.
Constant Throughput Timer
Slows threads down to reach a target number of requests per minute. It needs enough threads to be able to reach the target.
Precise Throughput Timer
A more accurate rate controller that spreads requests evenly and tries to hold the rate over a chosen period.
Think time versus pacing
These two ideas are often confused. Think time is the pause between steps inside one journey. Pacing is the pause between full iterations of the journey, designed so that each thread starts a new iteration at a regular interval. Pacing is especially useful when you want each user to perform a fixed number of journeys per hour. For example, if each user should complete one order every two minutes, pacing makes the thread wait until the two minutes are over, whether the order took 20 seconds or 60 seconds.
Think time is the time a diner spends reading the menu between ordering the starter and the main course. Pacing is the rule that says each table is seated every thirty minutes, regardless of how fast the previous table finished eating.
The effect on the thread count
Think time reduces the load that each thread creates. If one thread creates ten requests per second without pauses, it creates less than one request per second with a five-second pause between steps. This means that to produce the same load, you need many more threads. Teams that forget this either under-test their systems, because they keep a small thread count and add big pauses, or over-test them, because they keep a big thread count and no pauses.
Removing all timers “to make the test finish faster”. This turns each thread into a stress machine that has little to do with human behaviour. The numbers you report as “500 users” then describe something closer to several thousand real users, and your capacity conclusions become far too pessimistic.
Honest choice of think time
Choose think time from evidence, not from guesses. Look at your web analytics or application logs. Measure the real gaps between consecutive actions for the same session. Use the average as the centre and the spread as the random range. If you do not have data, document your assumption clearly in the test report so that readers can judge the result for themselves.
Example: online exam portal
Students spend about a minute reading each question and a few seconds clicking Next. Using a Gaussian timer centred near 60 seconds gives a realistic load, where the portal serves roughly one request per student per minute, instead of an unrealistic one request per second per student.
Example: stock trading screen
Traders refresh quotes every few seconds and place orders in bursts. Short think times of one to three seconds with occasional longer gaps model this behaviour, and the thread count must be calculated with these short cycles in mind.
9Closed and Open Workload Models
An advanced idea that explains why thread-based tests can flatter your system.
The closed model
The classic JMeter Thread Group follows the closed workload model. There is a fixed population of users. A user can send a new request only after the previous one is finished. If the server slows down, users wait longer, and so the number of new requests per second automatically falls. The system, in a sense, protects itself because the test slows down together with the application.
The open model
Real public websites behave according to the open workload model. New visitors arrive from the outside world at a certain rate, whether the server is fast or slow. If the server is struggling, visitors do not politely wait for earlier visitors to finish. They keep arriving, queues build up, and the system can collapse. The arrival rate is independent of how well the server is doing.
flowchart LR
subgraph Closed["Closed model: fixed number of threads"]
direction LR
C1["Thread sends request"] --> C2["Server responds"]
C2 --> C3["Thread thinks"]
C3 --> C1
end
subgraph Open["Open model: arrivals at a fixed rate"]
direction LR
O1["New visitors arrive at a set rate"] --> O2["Server queue"]
O2 --> O3["Server responds"]
end
A closed model is a school canteen with exactly 200 students. When the queue is long, students cannot bring more friends, so the pressure is limited. An open model is a street food stall during a festival: crowds keep arriving every minute whether or not the cook can keep up. The queue can grow without any limit.
Why this changes how you interpret thread counts
In a closed model test with a fixed number of threads, a slow server reduces the arrival rate. The server then gets a little relief, and the test may not show the full collapse that a real crowd would cause. This effect is sometimes called coordinated omission: the measuring tool, by waiting for slow responses, quietly leaves out the requests that should have been sent during the slow period, and it therefore records fewer slow samples than reality would produce. The measured percentiles look better than what real users would experience.
| Aspect | Closed model | Open model |
|---|---|---|
| What is fixed | Number of users | Arrival rate of users |
| When the server slows | Request rate falls | Request rate stays, queue grows |
| Typical systems | Internal tools, call centres, exam portals with a known user group | Public websites, APIs, ticket sales, news sites |
| Risk if chosen wrongly | Results look better than reality | Test may overwhelm itself with more threads than the generator can hold |
| JMeter support | Standard Thread Group | Open Model Thread Group and rate-driven setups |
Tools JMeter gives you for open-style tests
Recent JMeter versions include an Open Model Thread Group, where you describe a schedule of arrival rates such as “rise from zero to twenty arrivals per second over ten minutes, then hold for thirty minutes”. JMeter then creates as many threads as required to honour the schedule, up to a limit you choose. Alternatively, in the classic approach, teams combine a large enough thread count with a throughput timer, so that threads are always available and the timer decides the pace. Both methods aim to keep arrivals independent of server speed.
If your system is public-facing, ask yourself: “If the server became ten times slower tomorrow, would visitors stop coming?” If the honest answer is no, your workload is open, and you should plan rate-based tests with a generous pool of threads rather than a small fixed number.
10Choosing the Right Number: From Business Needs to Threads
A step-by-step method that replaces guesswork with arithmetic.
The question “how many threads should I use?” has no universal answer. It depends on your goal, your traffic and your system. But there is a reliable method that works for most projects. It moves from business facts, to throughput, to concurrency, to the final thread count.
Find the peak business load
Use analytics, logs or business forecasts to find the busiest hour you must support. Add expected growth for the next year or two. Use the peak, not the daily average, because systems fail at peaks.
Convert it into journeys per hour
Count how many complete user journeys, such as orders, log-ins or searches, happen in that busy hour. Split the load by journey type so that the mix is realistic.
Convert journeys into requests per second
Multiply journeys by the number of requests in each journey, then divide by 3,600 seconds. This gives the target throughput of the test.
Measure cycle time
Run a small pilot with a few threads to learn the typical response time. Add your chosen think time and pacing to get the cycle time.
Apply Little’s Law and add a margin
Multiply throughput by cycle time to get the threads needed. Add ten to twenty percent to leave room for slower responses during the test.
Check the generator and validate
Confirm that your load machines can hold the thread count. Run a short calibration test and compare the achieved throughput with the plan before starting the real run.
A full worked example
An online store expects 36,000 visits in its busiest hour next year. Each visit performs on average 12 requests. So the site must serve 432,000 requests in that hour, which is 120 requests per second. A pilot test shows an average response time of 0.8 seconds, and the team decides on an average think time of 6 seconds per request, because shoppers read and compare. The cycle time is 6.8 seconds. Threads needed are 120 multiplied by 6.8, which gives 816. Adding a fifteen percent margin brings the number to about 940, so the team plans for roughly 950 threads.
The concurrent-users shortcut
When you know the visits per hour and the average visit length, there is a shorter route. Concurrent users equal visits per hour multiplied by the average visit length expressed in hours. If 36,000 visits happen in an hour and each lasts 10 minutes, which is one sixth of an hour, then about 6,000 sessions are open at the same time. That is a different number from 950, because the shortcut counts every open session, including people who are only reading, while the threads in the earlier example send a request every few seconds. Both numbers are valid, but they answer different questions. Always state which one you use.
Do not copy a thread count from a blog post or from another project. A number such as “500 users” has meaning only together with the think time, response time and journey mix that produced it. Without these, it is just a number.
11Thread Counts for Different Test Types
The same scenario needs a different thread pattern depending on the question you are asking.
A performance test is not one thing. Teams run several kinds of tests, and each kind uses the thread count in its own way. Naming the test type clearly in your plan tells everybody what the thread number is supposed to prove.
Load test: “Can we handle the expected traffic?”
Use the thread count calculated from the real peak load of the business. Ramp up gradually, hold the load steady for a meaningful period, and ramp down. Success means response times and error rates stay within the agreed targets at the planned number of users.
Stress test: “What happens beyond the expected traffic?”
Increase the thread count in steps, for example 100 percent, 150 percent, 200 percent and 300 percent of the planned load. Watch which resource breaks first, how the system fails, and whether it recovers when the load returns to normal. The goal is to learn about the failure, not to pass.
Spike test: “Can we survive a sudden crowd?”
Use a very short ramp-up and a high thread count, then drop back quickly. This copies a flash sale, a viral post or a ticket release. It reveals auto-scaling delays, connection storms and cold caches.
Soak or endurance test: “Does it stay healthy for hours?”
Use a moderate thread count, often near the normal daily peak, and run for many hours. Slow problems such as memory leaks, growing log files, exhausted connections and gradual slowdowns only appear over time. The thread count here is modest, and the duration is the star.
Breakpoint test: “Where exactly is the limit?”
Keep adding threads at a steady pace until a clear failure condition appears, such as response time crossing a limit or errors passing a threshold. The thread count at that moment, together with the throughput, becomes your capacity number.
Baseline test: “What is normal for this build?”
Run with a tiny thread count, even one or five threads, to capture clean response times without any queueing. This baseline helps you separate slow code from slow capacity when you analyse heavier tests.
| Test type | Thread pattern | Ramp-up | Typical duration |
|---|---|---|---|
| Baseline | 1 to 5 threads | Immediate | 5 to 10 minutes |
| Load | Planned peak | Gradual | 30 to 60 minutes |
| Stress | Steps above the peak | Stepwise | 1 to 3 hours |
| Spike | Sudden jump | Very short | 10 to 20 minutes |
| Soak | Near the normal peak | Gradual | 4 to 24 hours |
| Breakpoint | Continuously rising | Long and steady | Until failure appears |
Run the baseline test first, every time. If a single thread already sees slow responses, adding more threads will only show you the same slowness with extra noise. Fix the single-user speed before you chase scalability.
12Advanced Thread Groups and Load Shapes
The standard Thread Group has one shape. Real tests often need many shapes.
The standard Thread Group ramps up once and then holds. That is fine for simple tests, but serious work often needs more control: steps, plateaus, bursts, and dips. JMeter offers several options for this, and some of them come from the well-known plugin collection that most performance teams install.
Standard Thread Group
One ramp-up and then a hold. Reliable and simple, but the thread count cannot be changed during the run without plugins.
Concurrency Thread Group
You set a target concurrency, a ramp-up time, a number of steps and a hold time. It adds threads as needed to reach that target. Often paired with a throughput shaping timer.
Ultimate Thread Group
A table of schedule rows, each with its own start delay, thread count, ramp-up, hold and shutdown time. Rows can overlap, which allows spikes, plateaus and dips in one group.
Stepping Thread Group
Adds batches of threads at regular intervals, then removes them in steps. Older and now largely replaced by the Concurrency Thread Group, but still seen in many projects.
Open Model Thread Group
Describes the arrival rate of users over time instead of a count of threads, and creates threads as needed up to a maximum.
Throughput Shaping Timer
Not a thread group, but a partner for one. It draws a target requests-per-second curve, and the threads follow it.
Fixed threads versus fixed rate
A key idea in this chapter is the difference between telling JMeter “use 500 threads” and telling it “deliver 100 requests per second”. With the first instruction you control concurrency, and the request rate follows the server speed. With the second you control the rate, and the number of active threads follows the server speed. Rate control with a pool of spare threads is the closest match to open-style traffic. It also keeps your test steady when the application slows down.
flowchart TD
A{"What do you want to control?"} -->|"Number of users"| B["Standard or Concurrency Thread Group"]
A -->|"Complex shape with spikes"| C["Ultimate Thread Group"]
A -->|"Requests per second"| D["Throughput Shaping Timer with a spare thread pool"]
A -->|"Arrivals of new visitors"| E["Open Model Thread Group"]
B --> F["Closed model behaviour"]
C --> F
D --> G["Rate driven behaviour"]
E --> G
Why spare threads are your friend
When you use a rate-driven setup, you must give JMeter enough threads to reach the rate in the worst case. If the response time doubles during the test, the threads needed also double. If you provide only the minimum, the rate will fall short exactly when the system is struggling, which is the moment you most want accurate pressure. Provide a margin, and then confirm from the active threads graph that the pool never ran empty.
Using a throughput timer with too few threads and then reporting “the system could only handle 40 requests per second”. In truth, the generator never had enough threads to send more. Always check whether the shortfall belongs to the test tool or to the application.
Installing and governing plugins
Advanced thread groups usually come from plugins, which means extra software on every load generator machine. Treat them like any other dependency. Keep the same plugin versions on the controller and on all workers, record the versions in the test report, and keep a copy of the files in your own storage in case the public source is not reachable on test day. A version mismatch between machines can produce silent errors that look like application problems.
13Sizing the Load Generator: CPU, Memory, Network and Garbage Collection
If your test machine is the weakest link, your results measure the machine, not the application.
Every thread you add costs the load generator something. If the generator runs out of CPU, memory or network capacity, requests are delayed inside JMeter itself. The recorded response time then includes time the application never caused. This is one of the most common reasons for misleading performance reports, and it is entirely avoidable with a little planning.
CPU
Used for building requests, encrypting secure traffic, running scripts and parsing responses. Secure connections and heavy scripting are the biggest consumers.
Memory
Each thread holds variables and results. Large responses, saved result data and many listeners can fill the Java heap quickly.
Network
The generator’s network card and the path to the server can saturate, especially when responses are large, such as images, files or big data pages.
Garbage Collection
When the heap is nearly full, the Java runtime pauses to clean memory. These pauses freeze all threads and appear as sudden latency spikes in the results.
The heap setting
JMeter starts with a modest default heap size, which is fine for development but too small for large tests. Increase the maximum heap in the startup configuration to a value that fits the machine, usually no more than about half to three quarters of the available memory, so the operating system still has room. Setting the minimum and maximum heap to the same value avoids resizing pauses during the run.
Practices that let one machine carry more threads
Run in non-GUI mode
The graphical interface uses a lot of memory and CPU. Use it to design and debug with a few threads, and use the command line to run real load.
Remove heavy listeners
Listeners that store every response, such as the tree view of results, are for debugging only. During load runs, write compact results to a file or a metrics backend and analyse afterwards.
Keep scripts light
Prefer built-in extractors and assertions over long scripts. Avoid assertions that read very large responses unless they are truly needed.
Do not save response bodies
Saving full responses for thousands of requests per second fills memory and disk. Save only what you need, such as time, status and label.
Watch the generator itself
Monitor CPU, memory, network and garbage collection on the generator during every run. Treat sustained CPU above roughly 70 to 75 percent as a warning that the generator is getting crowded.
More threads on one machine
- Simple setup with no coordination
- Easy to collect and read results
- Lower infrastructure cost
- Good for small and medium tests
Risks to manage
- Generator can become the bottleneck
- One machine is a single point of failure
- Network card may saturate before the server does
- Garbage collection pauses distort the timings
How to prove your generator is healthy
Run a calibration test. First, point the same test plan at a very simple, very fast endpoint, such as a static page on a healthy server. If response times at your target thread count are still tiny and stable, the generator is coping. If response times already show spikes there, the generator is the problem. This quick check separates tool limits from application limits, and it takes only a few minutes.
Place the load generator close to the application in network terms, ideally in the same region and the same network zone. A generator that sits across a slow link adds delay to every request, and the delay will appear in your results as if the application were slow.
14Distributed Testing: Multiplying Threads Across Machines
When one machine is not enough, use several, and remember that the thread count multiplies.
JMeter can control several machines from one place. One machine, the controller, sends the test plan to many workers, sometimes called remote engines. Each worker runs the plan on its own, generates load against the application and sends summary results back to the controller. This way you can reach very high loads that no single machine could produce.
flowchart TD
C["Controller: starts and stops the test, collects results"] --> W1["Worker 1: runs its own threads"]
C --> W2["Worker 2: runs its own threads"]
C --> W3["Worker 3: runs its own threads"]
W1 --> T["Application under test"]
W2 --> T
W3 --> T
W1 -.->|"results"| C
W2 -.->|"results"| C
W3 -.->|"results"| C
The multiplication rule
The most important fact about distributed testing is simple: every worker runs the whole Thread Group with the thread count you typed. If the Thread Group says 200 threads and you use five workers, the application receives 1,000 threads of load, not 200. Many teams are surprised by this and accidentally run ten times more load than planned. The same rule applies to loop counts and to every other setting inside the plan.
| Threads in plan | Workers | Total threads on the target | Planning note |
|---|---|---|---|
| 200 | 1 | 200 | Single machine |
| 200 | 5 | 1,000 | Five times the planned load |
| 40 | 5 | 200 | Divide the target by the number of workers |
| 1,000 | 10 | 10,000 | Ten times the planned load |
Typing the total thread count in the plan and then adding workers. Always divide the target total by the number of workers, and type the per-worker number. A good habit is to make the thread count a property that you pass at start time, so that each worker or each run can use its own value without editing the plan file.
Practical points for healthy distributed runs
Same versions everywhere
Use the same JMeter version, the same Java version and the same plugins on every machine to avoid strange differences.
Same data files
Test data files must exist on every worker. If every worker reads the same file, they may all use the same accounts at the same moment. Split the data so each worker has its own range.
Network openness
Controller and workers talk over several network ports, and a firewall between them is a very common cause of failed starts. Plan the network rules in advance.
Time alignment
Keep the clocks of all machines synchronised, so that timestamps in the combined results line up correctly.
Light result traffic
Choose a result sending mode that sends results in batches or as summaries, so the controller does not become a bottleneck by receiving every sample at once.
Equal workers
Use machines of the same size. Mixed hardware makes it hard to know which worker limits the result.
Containers and cloud generators
Many organisations run workers inside containers or on cloud machines that are created for the test and deleted afterwards. This gives flexible capacity and a clean state for every run. The same rules still apply: the machines must be close to the target in network terms, have stable resources, and be sized by a calibration test. Cloud machines that share hardware with other customers can have uneven CPU performance, so check the generator’s CPU during the run rather than trusting the label of the machine type.
Example: ticket sale preparation
A ticketing company expects 20,000 buyers at the moment a sale opens. A single generator can comfortably hold about 1,000 threads of their script, so they start twenty workers with 1,000 threads each, release them together, and monitor generator health to make sure results reflect the application, not the test fleet.
15What Threads Do to the Server: Queues, Pools and Saturation
Every extra thread travels through a chain of limited resources on the server side.
When your threads send requests, each request passes through a chain: a load balancer, a web server, an application server, perhaps a cache, and a database. Every stage has a limit, such as a maximum number of worker threads or database connections. As you raise the JMeter thread count, you push the chain until the weakest stage becomes full. Understanding the chain helps you read your results correctly.
flowchart LR
A["JMeter threads"] --> B["Load balancer"]
B --> C["Web server worker pool"]
C --> D["Application thread pool"]
D --> E["Database connection pool"]
E --> F["Database"]
The knee of the curve
As you increase the thread count, the system passes through three zones. In the first zone, response time stays flat and throughput rises almost in proportion to threads. In the second zone, the system approaches its limit: throughput rises more slowly and response time begins to climb. This turning point is often called the knee. In the third zone, the system is saturated: more threads add no extra throughput, because the extra requests only wait in queues, and response time grows steeply. Errors and timeouts often appear here.
Think of a motorway. With few cars, everyone drives at full speed. As traffic grows, speed stays good until the road is nearly full. Add a few more cars and speeds drop sharply. Past that point, more cars simply create a jam, and fewer cars per hour reach their destination than before.
Reading the capacity from the knee
The best working capacity of a system is usually found just before the knee, not at the point of total failure. If a system can technically serve 800 users before collapsing, but response times become unacceptable at 600, then the real capacity for the business is 600. Run a stepped test, plot response time and throughput against the number of active threads, and mark the knee. That mark is a much more useful result than a single pass or fail.
Queues hide in many places
When threads wait, they are waiting somewhere: in an operating system queue, in a web server queue, in an application pool queue, or for a database connection. A high response time with low CPU usage on the server is a classic sign that requests are waiting in a queue for a limited resource, such as a small connection pool. Raising JMeter threads further would make the wait longer without raising throughput. The cure is to find and widen the right pool or to speed up the slow dependency, not to add more load.
When response time rises as you add threads, check server CPU, memory, pool usage and database waits before blaming the code. The thread count tells you when the problem appears. The server metrics tell you why.
Connections and keep-alive
Each JMeter thread normally keeps its own connection to the server and reuses it between requests when keep-alive is enabled. So the thread count is roughly the connection count seen by the server and its load balancer. A load balancer with a limit of a few thousand connections may refuse new ones long before your application is stressed. Likewise, a firewall with connection tracking limits can drop connections. If errors begin suddenly at a round thread count, suspect such a limit in the network path.
16Monitoring, Metrics and Reading Results Against Thread Count
A number without a graph beside it is only half a result.
Your thread count is an input. To judge the test, you must watch the outputs and the health of everything in between. A good performance report always connects the number of active threads to the behaviour of the system at the same moment.
The core metrics to track
Active Threads
How many threads were really running at each second. Compare it with the number you planned.
Throughput
Completed requests or transactions per second. This shows the work the system actually did.
Response Time Percentiles
The ninetieth, ninety-fifth and ninety-ninth percentiles show what slow users experience. Averages hide the pain.
Error Rate
Percentage of failed requests, grouped by type, such as timeouts, server errors and assertion failures.
Connect and Latency Times
The time to open a connection and the time to the first byte. They help locate delays in the network or in the server.
Server Health
CPU, memory, disk, network, pool usage, garbage collection and database waits on every tier of the application.
Averages lie, percentiles tell the truth
Suppose 95 requests finish in 1 second and 5 requests take 20 seconds. The average is almost 2 seconds, which sounds acceptable, yet one in twenty users waited twenty seconds. Percentiles expose this. When you compare tests with different thread counts, compare the same percentile each time, and look at how the high percentiles grow as threads grow. The tail often begins to climb long before the average moves.
flowchart LR
A["Test plan with planned threads"] --> B["JMeter results file"]
A --> C["Generator health metrics"]
D["Server and database metrics"] --> E["One combined timeline"]
B --> E
C --> E
E --> F["Conclusion linked to thread count"]
Live dashboards and backend listeners
JMeter can send metrics, while the test is running, to an external time-series database and a dashboard tool. This lets you watch active threads, throughput and percentiles beside server graphs in real time. It also lets you stop a test quickly if something goes wrong. For after-the-run analysis, JMeter can generate an HTML dashboard report from the saved results file, which is a convenient way to share findings with people who do not use JMeter.
Security and data hygiene during load tests
Performance testing touches real systems and real data, so some safety rules apply. Never run high-thread tests against a production system without written approval and a clear schedule, because a successful test may look exactly like an attack to security tools and to other users. Use synthetic test accounts and fake data, never real customer details. Keep passwords and tokens out of shared test plan files, and pass them as protected parameters. Store result files carefully, because they can contain addresses, tokens and other sensitive values from requests and responses.
Inform the operations team, the security team, and any hosting or cloud provider that requires notice before you start a large test. A surprise load test can trigger automatic blocking, paging alerts at night, or real outages for other customers sharing the platform.
Example: unique data per thread
If all 500 threads log in with the same account, the application may lock the account or serve everything from one cached session, giving unrealistically good results. Provide a data file with 500 different test accounts so each thread acts as a different person, and make sure the data is not reused until all accounts have been used.
17Anti-patterns: Mistakes That Ruin Thread-Based Tests
Learn from the errors that experienced engineers have already paid for.
Problem
The team picks a round thread count such as 1,000 because it sounds impressive, with no calculation behind it and no think time.
Why It’s Harmful
The number has no link to real traffic. It may be far above the business need, causing needless fixes, or far below it, giving false comfort. Reports built on it cannot be defended in a review.
Correct Approach
Derive the thread count from peak throughput, response time and think time using the method in Chapter 10, and write the assumptions into the report.
Problem
All threads start at the same second with zero ramp-up for a normal load test.
Why It’s Harmful
The test measures a cold start shock instead of steady behaviour. Connection pools, caches and auto-scaling are all unprepared, so early errors dominate the results and hide the real capacity.
Correct Approach
Use a gradual ramp-up, exclude it from the measurement window, and run a separate spike test if sudden bursts are a real business risk.
Problem
The test runs from a laptop with the graphical interface open, many listeners enabled and thousands of threads.
Why It’s Harmful
The laptop becomes the bottleneck. Timings include delays inside JMeter, memory fills, garbage collection freezes the threads, and the report blames the application for a problem the tool created.
Correct Approach
Run in non-GUI mode on a well-sized, well-placed generator, use light result writing, and prove the generator is healthy with a calibration test and live monitoring.
Problem
In a distributed run, the same thread count is typed in the plan and then run on many workers without adjusting.
Why It’s Harmful
The real load becomes a multiple of the plan, so the team either crashes a test environment or reports numbers that belong to another load level.
Correct Approach
Divide the target thread count by the number of workers, make the value a property, and confirm the total from the active threads graph.
Problem
Reporting only the average response time and a single pass or fail.
Why It’s Harmful
Averages hide slow tails, and a single verdict hides how the system behaves as load grows. Decision makers lose the information they need for capacity planning.
Correct Approach
Report percentiles, throughput and errors against the thread count, mark the knee of the curve, and state the safe working capacity.
Problem
Threads stop themselves after the first error, and nobody checks the active threads during the run.
Why It’s Harmful
When the application fails, the load silently disappears, the system seems to recover, and the test looks successful even though it only stopped applying pressure.
Correct Approach
Choose an error action that keeps the load steady for capacity tests, and confirm the active thread curve is flat during the hold period.
18Best Practices and Real-World Examples
A practical checklist and four stories that show the ideas at work.
Best-practice checklist
Define what “user” means
Agree with the business on the meaning of users, sessions and concurrency before choosing numbers.
Start with a baseline
Measure single-user speed first, so you know what the application does without queueing.
Calculate, do not guess
Use throughput, response time and think time to compute the thread count, and add a margin.
Ramp up and measure at steady state
Use a gradual ramp, hold long enough, and analyse the flat period separately.
Use realistic data and pauses
Give every thread its own data, and use random think times based on real behaviour.
Protect the generator
Use non-GUI mode, tuned heap, light listeners, and a nearby, healthy machine or fleet.
Watch everything at once
Combine JMeter results, generator health and server metrics on one timeline.
Report with context
Write down thread counts per group, think times, ramp-up, versions and environment so the test can be repeated.
Real-world stories
Retail sale event
A large online retailer prepared for a festival sale. Analytics showed a peak of several hundred requests per second. The team calculated threads using realistic think times, ran stepped tests, found the knee at a database connection pool limit, enlarged the pool, and retested before the event. The sale opened without outages because the capacity number was based on evidence.
Banking portal after a release
A bank saw that a new release made log-in slower at 300 concurrent users, even though single-user tests were fine. Stepped thread tests showed response times doubling near 280 threads. Server metrics pointed to a slow call to an identity service that used a small pool. Fixing that pool restored the speed, and the bank could prove the fix with the same thread profile.
Government exam registration
A public portal expected a huge crowd at the opening minute of registration. Their traffic was open-style, so the team used rate-driven tests with a large spare thread pool across many workers. Early runs revealed that their own generators saturated at lower rates than planned, so they added workers until the generator CPU stayed low. Only then did the results describe the portal rather than the test machines.
Streaming service API
A video platform ran long soak tests with a moderate thread count over a full day. Memory use on an application server climbed slowly and caused restarts after ten hours. A short test would never have shown this. The steady, modest thread count allowed the team to observe a slow leak and fix it before launch.
19Frequently Asked Questions
Quick answers to questions that come up in almost every project.
Only if you add realistic think time and use realistic data. Without pauses, one thread can produce the load of many real people, so the claim “one thread equals one user” can be badly wrong.
There is no fixed number. It depends on the script weight, response sizes, secure traffic, heap size and CPU. Light scripts may carry thousands of threads, while heavy scripts may struggle with a few hundred. Find your own limit with a calibration test and live monitoring of the generator.
Long enough for the system to warm up and for the load to build smoothly, and shorter than the hold period. Many teams use a ramp-up of ten to twenty-five percent of the total test time, and then adjust after watching the first run.
Because the thread count was fixed. Each thread completes fewer cycles per second when responses slow down, so throughput falls. This is how closed-model tests behave. Use a rate-driven setup with spare threads if you need a steady request rate.
No. More threads mean more load, but not necessarily more value. Beyond the knee, extra threads only create queues and errors. The right number is the one that matches your goal, whether that is the expected peak, a stress step or a breakpoint search.
Concurrent users count people active together, including those who are thinking. Requests per second count the work the server completes each second. They are linked through response time and think time, using Little’s Law.
No. Each thread should have its own session so it behaves like a separate person. Sharing sessions makes the load unrealistic and may trigger caching or locking behaviour that real users would not cause.
Watch its CPU, memory, network and garbage collection during the run, and run a calibration test against a very fast endpoint. If timings are poor there, or CPU stays very high, the generator is limiting the test.
When your real traffic is open, meaning visitors arrive at a certain rate whether or not the server is keeping up. It lets you describe arrival rates directly and avoids the self-protecting effect of a fixed thread population.
The standard Thread Group does not support it. Advanced thread groups from plugins let you shape the thread count over time with steps, plateaus and spikes, and some can adjust the active count through a schedule defined in advance.
Every worker runs the full thread count typed in the plan, so the total is the thread count multiplied by the number of workers. Divide your target by the number of workers before you start.
Long enough to gather a large number of samples at steady state, often thirty to sixty minutes for a load test. Soak tests run for many hours, because slow problems need time to appear.
20Design Patterns for Thread-Safe Test Plans
Many threads running the same plan need careful handling of data and state.
When one thread runs a test, mistakes in data handling are easy to miss. When hundreds of threads run the same plan at the same moment, small design errors become loud failures. Good test plans follow a few simple patterns that keep every thread independent, so that failures you see belong to the application and not to a clash between your own threads.
Pattern 1: one data row per thread
Give each thread its own account, order number or search term from a data file. Configure the data reader so that every thread takes the next unused row, and decide in advance what happens when the file runs out. The reader can stop the thread, start again from the top, or stop the whole test. For a test with 500 threads running for an hour, a file with only 500 rows may be used up quickly if each loop consumes a row, so size the file with the loop rate in mind.
Think of a theatre where each guest needs a numbered ticket. If two guests hold the same number, they fight over one seat and the show is interrupted. A data file with unique rows is the ticket booth that hands each thread its own seat.
Pattern 2: correlate dynamic values per thread
Modern applications send values that change on every request, such as session identifiers, security tokens and form keys. Each thread must capture these values from the previous response and send them back in the next request. Captured values are stored in variables that belong to the thread, so threads do not mix their tokens. Never store such values in shared properties that all threads can read, because one thread could overwrite another thread’s token and cause strange, random failures that disappear when the thread count is low.
Pattern 3: separate shared and private state
Some things should be shared, such as a read-only list of product names, and some must be private, such as a shopping cart. Keep a clear boundary. Shared items should only be read during the run, never changed. Anything a thread changes belongs in its own variables. If you must share a counter, for example to generate unique order references, use a counter element that is designed for this and be aware that it can become a small point of contention under very high thread counts.
Pattern 4: realistic transaction grouping
Group the requests of one user action, such as a page and its images and scripts, under a named transaction. This lets you report the time of the whole action, which is what the user feels, instead of dozens of separate request times. When you compare tests with different thread counts, the transaction view is much easier to read and to explain to managers.
Pattern 5: controlled randomness in the user mix
Real users do not all follow the same path. Use controllers that pick a path at random with a defined probability, so that, for example, seventy percent of the iterations browse, twenty percent search and ten percent buy. This keeps one Thread Group realistic without needing many groups. Check afterwards, from the results, that the achieved mix is close to the intended mix, because a short test may drift from the plan by chance.
Unique data per thread
Avoids account locks, shared caches and unrealistic results.
Thread-local variables
Keeps tokens and sessions private and prevents random cross-thread failures.
Transaction grouping
Reports the time of a whole user action instead of many small requests.
Weighted path choice
Reproduces a believable mix of browsing, searching and buying inside one group.
Anti-pattern: global locks and synchronised sections
Some teams add scripts that lock a shared resource, for example to write to a common file or to wait for each other. Under high thread counts, such locks force threads to queue inside JMeter itself. The measured time then includes time spent waiting for a lock, not time spent waiting for the application. If you see throughput flatten at a surprisingly low value while server CPU is idle, search your plan for shared locks, shared file writes and long scripts.
Writing a line to a common log file from every thread looks harmless with ten threads. With a thousand threads it can become the slowest part of the whole test. Use the standard result writing and the metrics backend for output, which are designed to handle many threads.
Warm-up and cache behaviour
Applications behave differently on a cold start and on a warm run. The first requests after a restart fill caches, compile code paths and open connections. A fair test either includes a short warm-up that is excluded from the results, or deliberately tests the cold state as its own scenario. Likewise, if all threads request the same product again and again, the cache will answer almost every time, and your test will show speed that real users with a varied catalogue will never see. Use varied data so that the cache hit rate in the test is close to the rate seen in production.
Example: product search with varied terms
A team tested a search page with a single search term and saw lightning-fast results at 1,000 threads. In production, millions of different terms hit the search index, and most were not cached. After the team used a file of thousands of real search terms, the response time at 1,000 threads tripled, and the true capacity limit was revealed.
21Troubleshooting: Symptoms, Causes and Fixes
When the numbers look strange, start from the symptom and work toward the cause.
Thread-related problems tend to repeat across projects. The table below links the most common symptoms to their likely causes and the first thing to check. It will not replace careful analysis, but it saves time at the start of an investigation.
| Symptom | Likely cause | First check |
|---|---|---|
| Active threads lower than planned | Threads stopped after errors, loop count too small, or start failures | Error action setting, loop count and duration, and the start log |
| Throughput flat while response time rises | Server saturation or a full queue | Server CPU, pool usage and database waits |
| Throughput flat while server is idle | Generator limit, locks or too few threads for the target rate | Generator CPU, heap and garbage collection, and the active thread pool |
| Sudden latency spikes in all requests | Garbage collection pauses on the generator | Generator memory settings and collection logs |
| Errors begin at a round thread count | Connection limit in a load balancer, firewall or the server | Network device limits and open connection counts |
| Out-of-memory errors in JMeter | Heap too small, heavy listeners, saved response bodies | Heap setting, listener list and saved result fields |
| Results differ between runs | Cold caches, shared environment, uneven data | Warm-up, other users of the environment and data uniqueness |
| Distributed run produces too much load | Thread count multiplied by the number of workers | Per-worker thread value and total active threads |
| Many connection timeouts at start | Zero or very short ramp-up | Ramp-up period and connection pool warm-up |
| Login errors for many threads | Same account used by all threads, or accounts locked | Data file uniqueness and lockout rules |
A calm way to investigate
Confirm the load you really applied
Check the active threads and throughput graphs first. Many disputes end here, because the applied load was not what people thought.
Clear the generator
Look at the generator CPU, memory, network and garbage collection. If it is stressed, fix that and rerun before looking at the application.
Examine the errors
Group errors by type and time. Errors at the start suggest warm-up problems. Errors that begin at a certain thread count suggest a limit. Random errors suggest shared state.
Walk the chain on the server side
Follow the request path from the load balancer to the database, and compare each stage’s pool usage and waits against its limit.
Change one thing and rerun
Alter a single setting, such as a pool size or a thread count, rerun the same profile, and compare. Changing many things at once hides which one mattered.
Keep a short log of every test run: date, thread counts, ramp-up, duration, software versions, environment state and the main finding. After a few weeks this log becomes the best tool you have for spotting trends and for answering the question “did the last release make things slower?”
22Summary and Key Takeaways
The Number of Threads field looks tiny, but it carries the weight of your whole performance test. A thread is one independent worker that repeats your scripted journey. It is not a person, and it is not a request rate. How well it stands in for real users depends on think time, data, ramp-up, loop settings and the workload model. The right number comes from arithmetic, not from habit: peak throughput multiplied by the time of each cycle, with a margin for safety. Once chosen, the number must be supported by a healthy generator, confirmed by live monitoring, and interpreted together with server metrics and percentiles.
Key Takeaways
- A thread is a worker, not a person — it behaves like a user only when you add realistic think time, data and session handling.
- Calculate the count — threads needed equal target throughput multiplied by response time plus think time, with a ten to twenty percent margin.
- Ramp-up and hold matter — build load gradually, measure at steady state, and confirm the active thread curve is flat.
- Know your workload model — fixed threads give closed behaviour, while public traffic is usually open and needs rate-driven setups.
- Protect the generator — use non-GUI mode, light listeners, tuned memory and calibration tests so you measure the application, not the tool.
- Distributed tests multiply — every worker runs the full thread count, so divide the target by the number of workers.
- Read results against threads — use percentiles, throughput and server metrics to find the knee and state the safe working capacity.