JMeter Samplers Explained
A beginner-friendly but production-minded guide to the building blocks that actually send requests, measure responses, and turn a test plan into real load on a real system.
Picture a brand-new online shop that is about to launch a big sale. The team has written the code, the servers are ready, and everyone is excited. But one question keeps the engineers awake at night: what happens when ten thousand people click “Buy” in the same minute? Will the pages still load quickly, or will the whole site slow to a crawl and send customers away?
Apache JMeter is a free, open-source tool that answers this question before real customers ever arrive. It pretends to be many users at once and measures how the system behaves. And inside JMeter, there is one kind of building block that does all of the real work of “pretending”: the sampler. If you understand samplers, you understand the heart of JMeter. Everything else in a JMeter test plan exists either to help a sampler do its job or to help you understand what the sampler found.
This guide teaches samplers from zero. We start with what a sampler is, follow a single request through JMeter’s internals, study the results it produces, tour the most important sampler types, and then move on to the topics that separate a beginner script from a production-quality load test: parameterization, scalability, security, monitoring, design patterns, and common mistakes. Every new term is explained the first time it appears, and every big idea comes with an everyday comparison.
WWhat Is a Sampler in JMeter?
A sampler is the one part of a JMeter test that actually talks to the outside world.
Apache JMeter is a desktop application written in Java. It was first created by Stefano Mazzocchi at the Apache Software Foundation to test the performance of an early Java web server technology, and it has since grown into one of the most widely used open-source load-testing tools. Over the years it gained support for many protocols, not just web pages.
In JMeter, a test is described by a test plan. A test plan is a tree of small elements, and each element has a job. Some elements describe how many virtual users to run. Some hold settings. Some check answers. But exactly one family of elements sends something to the system you are testing and waits for a reply. That family is the samplers.
The simple definition
A sampler is a test-plan element that sends one request to a server, waits for the response, and records what happened. The word “sample” is used the way a scientist uses it: one measurement taken from a larger population. Each time a sampler runs, it takes one measurement of how the system behaves. Run it thousands of times with many virtual users, and you have thousands of samples that together describe the system’s performance.
Think of a mystery shopper hired by a restaurant chain. The shopper walks in, orders a meal, and notes how long the waiter took, whether the food was hot, and whether the bill was right. Each visit is one “sample” of the restaurant’s service. A JMeter sampler is that shopper, except it can visit thousands of restaurants at the same moment and never gets tired.
What a sampler does, step by step
Every sampler, no matter the protocol, follows the same basic pattern. First, it builds a request from its settings, such as a web address, a database query, or a message body. Second, it sends that request over the network. Third, it waits for the reply and notes exactly how long each stage took. Fourth, it packages everything it learned into a record called a sample result. That record then flows to other elements in the plan, which can check it, extract values from it, or display it.
A key idea to remember is that samplers are the only elements that produce measurements. Timers produce delays. Assertions produce pass or fail verdicts. Config elements produce settings. Only samplers produce the timing and size data that a performance report is built on.
One sampler run usually creates one sample result, but it can create more. A web request that downloads embedded images, or a transaction that groups several requests, can produce a parent result with child results inside it. We will see how this works in later chapters.
Why samplers matter so much
If your samplers are wrong, nothing else can save the test. A sampler that points at the wrong address measures nothing useful. A sampler that sends unrealistic data produces numbers that do not match real life. And a sampler that is configured carelessly can quietly slow down your load generator, so you end up measuring your own test machine instead of the system under test. Good samplers are the foundation of every trustworthy performance test.
WWhere Samplers Live: The Test Plan Tree
Samplers never stand alone. Their position in the tree decides how often they run and which helpers affect them.
A JMeter test plan is arranged as a tree. At the top sits the Test Plan itself. Under it you add one or more thread groups. A thread group is the element that defines your virtual users. In JMeter each virtual user is a Java thread, which is why the word “thread” is used so often. The thread group says how many threads to start, how quickly to start them (the ramp-up period), and how many times each thread should repeat the work beneath it.
Samplers are placed underneath a thread group, either directly or inside controllers. When the test runs, every thread walks down the tree from top to bottom, running each sampler it meets, one after another. Then it starts again from the top if the loop count says so. This is why a sampler’s position matters: it decides when in the user’s journey the request happens.
flowchart TD
TP["Test Plan"] --> CE["Config Elements: HTTP Request Defaults, Cookie Manager, CSV Data Set"]
TP --> TG["Thread Group: users, ramp-up, loops"]
TG --> LC["Logic Controller: Transaction, Loop, If"]
TG --> S1["Sampler: Home Page"]
LC --> S2["Sampler: Login"]
LC --> S3["Sampler: Search"]
S2 --> AS["Assertion: Response Check"]
S3 --> EX["Post-Processor: Extractor"]
TG --> LI["Listener: Results and Reports"]
The three special thread groups
Besides the normal Thread Group, JMeter offers a setUp Thread Group and a tearDown Thread Group. Samplers placed in setUp run once before the main test, which is perfect for creating test data or getting an access token. Samplers placed in tearDown run once after the main test finishes, which is ideal for cleaning up. Think of setUp as setting the tables before the restaurant opens and tearDown as washing the dishes after it closes.
Scope: who affects whom
The tree also controls scope. A helper element such as a timer, an assertion, or a header manager affects the samplers that sit at the same level or below it, within the same branch. If you place a header manager at the thread group level, every sampler in that group receives those headers. If you place it as a child of one sampler, only that sampler is affected. Scope is one of the most common sources of confusion for beginners, so it is worth learning early.
| Where the helper is placed | Which samplers it affects | Typical use |
|---|---|---|
| Directly under the Test Plan | Every sampler in every thread group | Shared defaults and global listeners |
| Under a Thread Group | All samplers in that group | Cookie handling, common headers |
| Under a Controller | Only samplers inside that controller | Think time for one section of the journey |
| As a child of one Sampler | Only that single sampler | A one-off assertion or extractor |
A timer placed at thread group level delays every sampler in the group, not just the one you were thinking about. Many beginners add one pause and then wonder why their entire test is slow. Always ask: “What is the scope of this element?”
HHow a Sampler Works Internally
Follow one request from the moment a thread reaches it to the moment its result is stored.
Understanding the internal order of events removes a lot of mystery. When a virtual user’s thread reaches a sampler in the tree, JMeter does not simply fire the request. A small chain of things happens in a fixed order, and knowing that order helps you place elements correctly and debug strange behaviour.
Configuration elements are applied
Settings such as default server names, cookies, headers, and data file values are prepared so the sampler can use them.
Pre-processors run
Small helpers modify the request or prepare variables before it is sent, for example setting a timestamp or a random number.
Timers run
Any timers in scope make the thread wait. This is how “think time” is added so virtual users behave like real people.
The sampler sends the request
The sampler opens or reuses a connection, sends its data, and waits for the response while a clock measures each stage.
Post-processors run
Helpers pull useful values out of the response, such as a session token or an order number, and store them in variables.
Assertions run
Checks decide whether the response is acceptable. A fast but wrong answer is marked as a failure.
Listeners receive the result
The finished sample result is handed to listeners, which display it, summarize it, or write it to a file.
This order is documented by the JMeter project and is worth memorizing. One detail surprises people: timers, pre-processors, post-processors, and assertions only run if there is a sampler to attach them to. A timer sitting alone in an empty branch does nothing at all.
flowchart LR
T["Virtual User Thread"] --> C["Config Elements"]
C --> P["Pre-Processors"]
P --> M["Timers"]
M --> S["Sampler sends request"]
S --> SYS["System Under Test"]
SYS --> R["Response returns"]
R --> PP["Post-Processors"]
PP --> A["Assertions"]
A --> L["Listeners"]
Threads are independent
Each virtual user runs on its own thread, and each thread has its own copy of variables. This is important. When one thread extracts a session token and stores it in a variable, other threads do not see it. That independence is what lets thousands of “users” each log in with their own identity at the same time. Values that are meant to be shared across all threads are stored as properties instead of variables, but that is a more advanced technique.
Samplers run one at a time per thread
Within a single thread, samplers run strictly one after another. The thread sends a request, waits for the full reply, and only then moves on. This matches how a real person uses a website: you click, wait for the page, and click again. The parallelism in JMeter comes from having many threads, not from one thread doing many things at once. The main exception is a web request configured to download embedded resources in parallel, which we cover in the HTTP chapter.
Imagine a theme park with a thousand visitors. Each visitor follows a personal list of rides and finishes one ride before joining the next queue. The park becomes busy because there are many visitors, not because any single visitor is riding two rides at once. Threads are the visitors and samplers are the rides.
The measured time for a sample covers only the sampler’s own request and response. Time spent in timers is not counted as response time, which is exactly what you want: think time is a pause between actions, not a slow server.
AAnatomy of a Sample Result
Every sampler run leaves behind a record. Learning to read it is half of performance testing.
After a sampler finishes, JMeter creates a sample result. Think of it as a small report card for that one request. It holds the name of the sampler, the time it took, whether it passed, the size of the data, and much more. When you open a results file or look at a listener, you are looking at a collection of these report cards.
| Field | Plain-English meaning |
|---|---|
| Label | The name of the sampler. Reports group results by this name, so clear names matter. |
| Elapsed time | Total time from just before sending the request to just after the last byte of the response arrived. |
| Latency | Time from just before sending the request to just after the first byte of the response arrived. |
| Connect time | Time spent opening the network connection to the server. |
| Response code | The status the server returned, such as 200 for success or 500 for a server error. |
| Response message | A short text that goes with the code, such as OK. |
| Success flag | True or false. It combines the response code with the verdicts of any assertions. |
| Bytes and sent bytes | How much data was received and how much was sent. |
| Thread name | Which virtual user produced the sample. |
| Active threads | How many threads were running at that moment, in the group and in total. |
| Timestamp | When the sample started. |
Elapsed time, latency, and connect time
These three timing values confuse almost every beginner, so let us make them concrete. Imagine ordering a pizza by phone. The connect time is how long it took the phone to start ringing and someone to pick up. The latency is how long until you hear the very first word of their answer. The elapsed time is how long until the whole conversation is finished and you have heard everything.
For a tiny response, these numbers are almost the same. For a large download, elapsed time is much longer than latency because the body takes time to arrive. When a report says “response time”, it is almost always referring to elapsed time. Comparing latency to elapsed time can also hint at where the slowness lives: a high latency suggests the server is slow to start answering, while a large gap between latency and elapsed time suggests a slow or heavy data transfer.
If connect time is large on every request, the system may not be reusing connections. Check that keep-alive is enabled on your HTTP samplers and that the server allows it.
What counts as success?
By default, a web sampler treats response codes in the 2xx and 3xx ranges as successful and everything else, such as 404 or 500, as a failure. But a server can return a perfectly healthy 200 code with an error message hidden in the page body, such as “Your session has expired”. Because of this, you should almost always add an assertion that checks the content of the response. A sample is only truly successful when the status looks right and the content looks right.
A delivery person knocks, hands over a sealed box, and says “Delivered!” The status is fine, but you only know it worked when you open the box and see the right item inside. The response code is the knock; the assertion is opening the box.
Failures are data, not noise
A failed sample is not something to hide. Failures tell you where the system breaks under load. A healthy test shows an error rate close to zero at moderate load and a rising error rate as you push harder. The shape of that rise, and which samplers fail first, are often the most valuable findings of the whole exercise.
TThe HTTP Request Sampler in Depth
The workhorse of JMeter. Most tests for websites and APIs are built almost entirely from this one sampler.
HTTP, short for HyperText Transfer Protocol, is the language that browsers and web servers use. Every web page, every mobile app screen backed by a web service, and every REST API call travels over HTTP or its secure form, HTTPS. The HTTP Request sampler lets JMeter speak this language.
The main settings
Protocol, Server, Port
Say whether to use http or https, which host to contact, and which port. If left blank the usual defaults of 80 and 443 apply.
Method and Path
The method is the action, such as GET to read or POST to send data. The path is the page or endpoint being called.
Parameters and Body
Form fields can be listed as name and value pairs. For JSON or XML APIs, the whole message is placed in the body data tab.
File Upload
Lets a virtual user upload a file, which is useful for testing profile photos, documents, or imports.
Redirects and Keep-Alive
Choose whether to follow redirects and whether to keep the connection open between requests, as browsers normally do.
Timeouts
Connect and response timeouts stop a stuck request from freezing a thread forever. Always set them.
Methods in everyday language
A GET request asks the server to give you something, like opening a product page. A POST request sends new information, like submitting a login form or placing an order. A PUT request replaces something that already exists, and a DELETE request removes it. Real systems treat these actions very differently under load. Reading is usually cheap and cacheable, while writing touches databases and locks, so a realistic test mixes both in the proportions real users produce.
Embedded resources
A real web page is not one file. It is an HTML document that points to images, style sheets, and scripts. A browser downloads them all. The HTTP sampler has an option called Retrieve All Embedded Resources that makes JMeter do the same, and an option to download them in parallel using several connections, as browsers do. The results appear as child samples under the main page sample.
Turn this on when you want to measure full page load behaviour, including the load on a content delivery network or static file servers. Leave it off when you are testing a back-end API, because an API response has no embedded resources and the extra work is wasted. Many teams also exclude third-party addresses such as analytics scripts, because load-testing someone else’s service is both pointless and rude.
Two implementations
The sampler can use either its older built-in Java implementation or the Apache HttpClient implementation, which is the default in recent versions. HttpClient supports more features, such as better connection reuse and more precise control. Unless you have a specific reason to change it, keep the default.
Supporting elements you will almost always add
- HTTP Request Defaults holds the server name, port, and protocol once, so you do not repeat them in fifty samplers. When the test moves from a staging server to a production-like server, you change one place.
- HTTP Cookie Manager stores and sends cookies automatically, exactly like a browser. Without it, every request looks like a stranger, and login sessions break.
- HTTP Header Manager adds headers such as content type and authorization tokens to requests.
- HTTP Cache Manager imitates a browser’s cache so repeated requests for the same static file are not downloaded again.
- DNS Cache Manager controls how host names are resolved to addresses, which matters when testing load-balanced hosts.
If you record a browser session and replay it without cookie handling, every virtual user may appear to log in from nowhere, and the server may reject them. Always add a cookie manager to web tests and check one successful login with a results viewer before scaling up.
A realistic example
Testing an online store’s checkout journey
A sensible plan uses one thread group with about five samplers in order: open the home page, search for a product, view the product page, add it to the basket, and check out. Each is an HTTP Request sampler. A Cookie Manager keeps the session alive, a Response Assertion verifies the basket contains the right item, and a timer adds a few seconds of think time between steps so the test looks like real shoppers instead of a machine gun.
CConnections and the Network Life of a Sample
Before a single byte of your request travels, several invisible steps must happen. Knowing them explains many strange timing numbers.
When a sampler sends a web request, it does not simply “send a message”. It must first find the server, open a connection, and, for secure sites, agree on how to encrypt the conversation. Each of these steps takes time and each can fail separately. A sampler that seems slow may actually be slow at only one of these steps.
Name lookup
The host name, such as shop.example.com, must be turned into a numeric network address. This is done by a naming service called DNS. Results are often remembered for a while so later lookups are instant.
Connection setup
A TCP connection is opened. Both sides exchange a short greeting so they know the other is ready. This is the stage reported as connect time.
Secure handshake
For HTTPS, the two sides exchange certificates and agree on encryption keys. This costs extra round trips, and for HTTPS the connect time generally includes it.
Request and response
The sampler finally sends its request, the server works on it, and the reply streams back. Latency and elapsed time describe this stage.
Keep or close
If keep-alive is on, the connection stays open for the next request. Otherwise it closes, and the next sampler must repeat steps two and three.
Calling a business is like this too. First you look up the number, then you dial and wait for someone to pick up, then you confirm who you are, and only then do you ask your question. If you hang up after every question and dial again, you waste most of your time on the greetings. Keep-alive is staying on the line for your follow-up questions.
Why connection reuse changes the numbers
A real browser reuses connections. If your test closes the connection after every request, it forces the server to accept a flood of new connections and to perform a costly secure handshake each time. Your test then reports slower responses and higher server load than real users would ever cause. Conversely, if your test reuses a single connection forever, it hides the cost of connection setup that new visitors actually pay. The honest approach is to match real behaviour: reuse connections within a user’s session, and start fresh for each new virtual visitor.
JMeter has settings that control whether connections and secure-session memory are reset at the beginning of each loop of a thread group. A fresh iteration usually represents a new visitor, so many tests want this reset. Understanding which behaviour is active in your version prevents very confusing differences between your test and production traffic.
Limits of the load generator’s operating system
Every open connection uses a slot on the machine. Operating systems limit the number of simultaneously open files and network ports. When a test opens and closes huge numbers of connections quickly, the machine may run out of available local ports because closed connections linger for a short time before the port is freed. The symptom looks like sudden bursts of “cannot assign address” or connection errors on the generator, even though the server is healthy. The cure is a mix of connection reuse, adjusted operating-system limits, and spreading the load over more engines.
Errors that mention a connection problem on the client side do not always mean the server is broken. Check the load generator first. If its own limits were reached, the test, not the system under test, was the weak link.
Proxies, source addresses, and load balancers
The HTTP sampler can be told to go through a proxy server and to use a particular local network address. Using several source addresses can help when a load balancer spreads traffic by client address; if every virtual user comes from one address, a balancer may send all of them to the same back-end machine and your test would hit only one server. Knowing how the target distributes traffic helps you design a test that exercises the whole farm and not just one corner of it.
AAuthentication and Sessions
Most real systems need users to prove who they are. Samplers must do this correctly and repeatedly, without turning login into the only thing you test.
Authentication is the process of proving identity, and a session is the period during which the system remembers that proof. Performance tests that skip this step often exercise only public pages and miss the heavy work behind the login. Tests that handle it badly can accidentally measure only the login system.
Common ways systems recognize a user
Session cookies
The server hands out a cookie after login, and the browser sends it back each time. A Cookie Manager does this automatically in JMeter.
Bearer tokens
The login response contains a token, and each later request carries it in an authorization header. Extract it once and add it with a Header Manager.
Basic and Digest login
The Authorization Manager stores credentials for a given address so JMeter can answer a server’s challenge automatically.
Windows and Kerberos sign-in
JMeter supports these schemes through the Authorization Manager and extra configuration, which is common in corporate networks.
Login once, or login every time?
This is a modelling decision, not a technical one. In real life, a person logs in once and then performs many actions. So the usual design places the login sampler inside a Once Only Controller, or in a setUp group, and reuses the session for the rest of the journey. If you put login inside every loop, you overload the authentication service with far more login traffic than a real day would ever bring, and you will conclude that the login system is a bottleneck when it is only a test artefact.
On the other hand, if the real system has a high rate of fresh logins, such as an online exam starting at nine o’clock, then login is exactly the thing to stress. Decide based on how the real users behave, and write down your choice in the test plan so the next reader understands it.
Tokens expire
Tokens usually have a lifetime. A test that runs for an hour while its token lasts ten minutes will begin failing halfway through with authorization errors. There are two common solutions. One is to refresh the token on a schedule, using an If Controller that checks how long ago it was issued. The other is to design each loop to fetch a fresh token when it needs one. Either way, assertions are essential, because an expired token often produces a polite error page with a normal status code.
Hidden form values and anti-forgery tokens
Many websites include a hidden value in every form, designed to stop forged requests. This value changes for each user and each page. If your script replays a recorded value, the server rejects it. The fix is correlation: extract the hidden value from the page that contains the form, store it in a variable, and send it with the form submission.
Some protections, such as image puzzles and one-time codes sent to a phone, are designed to stop automation. Do not try to defeat them. Instead, ask the owners of the test environment for a supported way around them, such as a special test account or a switch that turns them off for load testing.
Sticky sessions
Some systems keep a user’s session in the memory of one particular server, and the load balancer sends that user back to the same machine each time, usually based on a cookie. When virtual users share one cookie or lose their cookies, the pattern changes and the test no longer resembles real traffic. Giving each user their own cookies, and clearing them between iterations when a new visitor is intended, keeps the distribution honest.
TThe Sampler Family: Beyond HTTP
JMeter can test far more than websites. Each protocol has its own sampler, and each follows the same send, wait, record pattern.
A protocol is simply an agreed set of rules for how two computers talk. Web pages use HTTP. Databases speak their own protocols. Message queues, mail servers, and file servers each have theirs. JMeter ships with a sampler for many of them, so the same tool can test a whole architecture, not just its front door.
flowchart TD
J["JMeter Samplers"] --> W["Web Family"]
J --> D["Data Family"]
J --> M["Messaging and Mail"]
J --> N["Network and Files"]
J --> U["Scripting and Utility"]
W --> W1["HTTP Request"]
W --> W2["GraphQL HTTP Request"]
W --> W3["AJP 1.3 Sampler"]
D --> D1["JDBC Request"]
D --> D2["LDAP Request"]
D --> D3["Bolt Request"]
M --> M1["JMS Publisher"]
M --> M2["JMS Subscriber"]
M --> M3["SMTP Sampler"]
M --> M4["Mail Reader"]
N --> N1["TCP Sampler"]
N --> N2["FTP Request"]
U --> U1["JSR223 Sampler"]
U --> U2["Debug Sampler"]
U --> U3["Flow Control Action"]
The most useful protocol samplers
JDBC Request
Runs SQL queries against a relational database. It needs a JDBC Connection Configuration element that holds the connection pool settings, and the database driver file must be added to JMeter’s classpath.
JMS Publisher and Subscriber
Send messages to a queue or topic and read them back. A third variant, JMS Point-to-Point, covers request and reply style messaging.
LDAP Request
Tests directory servers used for logins and user lookups, common in corporate environments.
SMTP Sampler and Mail Reader
Send email through a mail server, and read messages from a mailbox using POP3 or IMAP.
TCP Sampler
Opens a raw network connection and exchanges text or bytes. It is the answer when no ready-made sampler exists for a custom protocol.
FTP Request
Downloads or uploads files from a file transfer server and measures how long it takes.
Access Log Sampler
Reads a web server’s access log and replays real traffic patterns, a handy way to reproduce what real users actually did.
Java Request and JUnit Request
Run your own Java test code inside JMeter so you can load-test libraries and services that have no network protocol sampler.
JDBC in plain language
A database is like a huge library with a very fast librarian. A SQL query is a note you hand the librarian asking for certain books. The JDBC Request sampler hands thousands of such notes to the librarian at once. To avoid wasting time opening a new door for every note, JMeter uses a connection pool, which is a fixed group of already-open connections that threads share. Sizing the pool matters: too small and threads queue up waiting for a connection; too large and you may overwhelm the database in a way real traffic never would.
Messaging in plain language
A message queue works like a post office. One program drops a letter in a box, and another program collects it later. The JMS samplers can act as the sender, the receiver, or both. They are used to test whether the broker, the system that holds the messages, can keep up as the number of messages per second grows, and how long a message takes to travel end to end.
When two sampler types could both do the job, prefer the one closest to the real client. Testing a web service with a TCP sampler means you must hand-write the HTTP rules yourself and will probably get them wrong. The matching sampler already knows the protocol correctly.
Why variety is useful
Real systems are chains. A user request hits a web server, which calls a service, which reads a database and publishes a message. A good performance engineer tests each link separately with its own sampler to find which link is weakest, and then tests the whole chain together through the web sampler to see how links interact. Having many sampler types makes this layered approach possible inside a single tool.
SScripting and Utility Samplers
Some samplers do not talk to a server at all. They help you shape the test itself.
JSR223 Sampler
Sometimes no ready-made sampler can do what you need. Maybe you must call a custom signing routine, compute a checksum, or run a multi-step calculation that produces a sample result. The JSR223 Sampler lets you write a short script that acts as a sampler. JSR223 is a standard that allows many scripting languages to run inside Java programs. For JMeter, the recommended language is Groovy, because it is fast and close to Java.
The sampler can also set the response data, the response code, and the success flag itself, so your own code produces a proper sample result that reports and assertions treat like any other. A performance setting called caching the compiled script is important here. When enabled, JMeter compiles the script once and reuses it, instead of recompiling for every run. Without it, a busy script can become a hidden bottleneck on the load generator.
The older BeanShell sampler still exists for backwards compatibility, but it is much slower than JSR223 with Groovy. New test plans should use JSR223.
Debug Sampler
The Debug Sampler does not send anything. Instead, it produces a sample result that shows the current values of JMeter variables, properties, and system settings. It is like a mirror held up to your test: you can see exactly which token was extracted or whether a data file value was loaded correctly. Use it while building a script. Disable or remove it before running a heavy test, because it adds noise and extra work.
Flow Control Action
This sampler, called Test Action in older versions, does not generate a sample result at all. It controls the flow of the thread. It can pause a thread for a set time, stop a thread, stop the whole test, or jump to the next loop iteration. It is useful for graceful exits, such as stopping the test early when a login failure shows that continuing would be pointless.
OS Process Sampler
The OS Process Sampler runs a command on the machine where JMeter is running and records how long it took and what it returned. It can be handy for calling a helper program or triggering a script, but it comes with real security considerations, which we discuss in the security chapter. Treat it with the same care as running any command-line tool.
If protocol samplers are the actors on a stage, utility samplers are the stagehands. They never face the audience, but they move scenery, check the lights, and make sure the show runs smoothly.
When scripting becomes a smell
Scripting is powerful, and that power is dangerous. If your test plan is mostly script samplers, you have probably rebuilt a custom test framework inside JMeter’s tree, which makes it harder to read, harder to share, and harder to run at scale. Prefer the built-in samplers and extractors whenever they can do the job, and use scripts only for the small gaps they cannot fill.
CControllers: Deciding When Samplers Run
Samplers do the work. Controllers decide the order, the repetition, and the conditions.
A logic controller is an element that groups samplers and decides how they are executed. Without controllers, a thread just runs samplers top to bottom. With controllers, you can repeat steps, run steps only sometimes, pick steps at random, and group steps into named business actions.
| Controller | What it does | Typical use |
|---|---|---|
| Simple Controller | Just groups samplers for tidiness | Organizing a long plan |
| Loop Controller | Repeats its children a set number of times | Add five items to a basket |
| If Controller | Runs children only when a condition is true | Skip login if a token already exists |
| While Controller | Repeats while a condition holds | Poll a job until it finishes |
| Once Only Controller | Runs children once per thread | Login at the start of the journey |
| Random Controller | Picks one child at random each time | Mix different user behaviours |
| Throughput Controller | Runs children for a given percentage of iterations | Only 20 percent of users check out |
| ForEach Controller | Loops over a list of extracted values | Open each product from a search result |
| Transaction Controller | Groups samplers into one measured business step | Measure the whole “checkout” as one number |
The Transaction Controller deserves special attention
Users do not think in single requests. They think in actions such as “log in”, “search”, or “pay”. A Transaction Controller wraps several samplers and reports their combined time as one result. If checkout involves six requests, the transaction gives you a single “Checkout” number, which is far more meaningful to a business reader than six technical lines.
It has an option to generate a parent sample. When this is on, the group appears as one row in the report and the individual samplers are hidden as children. When it is off, the group’s total is reported as an extra row alongside the individual rows. Another option controls whether timer pauses and processing time are included in the transaction’s total. For clean business timing, you usually want think time excluded.
flowchart TD
TG["Thread Group"] --> OO["Once Only Controller"]
OO --> L["Sampler: Login"]
TG --> TC["Transaction Controller: Checkout"]
TC --> A["Sampler: Add to Basket"]
TC --> B["Sampler: Enter Address"]
TC --> C["Sampler: Pay"]
TG --> IF["If Controller: user wants gift wrap"]
IF --> G["Sampler: Add Gift Wrap"]
Realistic user mix
Real traffic is never uniform. On a typical shopping site, most visitors only browse, a smaller group searches, and a still smaller group buys. If your test makes every virtual user buy something, you will heavily overstate the load on the checkout and payment systems and understate the load on search and browsing. Throughput and Random controllers let you reflect the real proportions. The numbers for these proportions should come from production analytics wherever you can get them, not from guesses.
Name every controller and sampler clearly, such as “01 Login” or “Checkout: Pay”. Reports are built from these names, and good names make results readable by people who never opened your script.
TThe Helpers Around a Sampler
Config elements, timers, processors, and assertions turn a bare request into a realistic, checkable one.
Config elements: settings in one place
A config element stores settings that samplers read. HTTP Request Defaults, the Cookie Manager, the Header Manager, the JDBC Connection Configuration, and the CSV Data Set Config are all config elements. They keep your test plan tidy because you define a setting once and every sampler in scope uses it.
Timers: realistic pauses
Without pauses, a virtual user fires the next request the instant the last one ends. A real person reads, thinks, and types. This pause is called think time. A timer adds it. Without timers, a few hundred threads can generate a request rate that thousands of real users never would, and a test that looks scary may actually be unrealistic.
Constant Timer
Waits the same time every time. Simple but robotic.
Uniform and Gaussian Random Timers
Wait a varying time around a base value, which looks more human and avoids all threads acting in lock-step.
Constant Throughput Timer
Adjusts pauses so the test achieves a target number of requests per minute.
Synchronizing Timer
Holds threads until a group is ready, then releases them together to simulate a sudden spike.
Pre-processors and post-processors
A pre-processor runs just before a sampler and prepares things, such as generating an order number. A post-processor runs after the sampler and reads the response. The most common post-processors are the extractors: the JSON Extractor, the Regular Expression Extractor, the CSS Selector Extractor, the XPath Extractor, and the Boundary Extractor. They pull a value out of a response and store it in a variable for later use. This is the foundation of correlation, which we cover in the chapter on parameterization and correlation.
Assertions: the quality check
An assertion decides whether a response is acceptable. The Response Assertion looks for expected text or codes. The JSON Assertion checks a value inside a JSON message. The Duration Assertion fails any request slower than a limit. The Size Assertion checks the number of bytes returned. Assertions cost a small amount of processing time, so use them where they matter and keep them simple, but never skip them entirely.
Helpful assertion habits
- Check for text that only appears when the action truly worked
- Check the JSON field that holds the business result
- Use a duration assertion only when you have a real service level target
- Name assertions so failures explain themselves
Assertion habits to avoid
- Checking only the status code and nothing else
- Asserting on text that appears in error pages too
- Using heavy regular expressions on huge responses
- Attaching a costly assertion to every sampler without need
Putting the helpers together
Consider a login sampler. A config element supplies the server address and cookie handling. A CSV data source supplies a different username for each virtual user. A pre-processor generates a timestamp. A timer waits three seconds, as a person would while typing. The sampler sends the request. A JSON Extractor stores the returned token. A Response Assertion confirms the text “welcome”. A listener records the whole thing. Seven elements cooperate around one sampler, and each does exactly one job.
EError Handling: When a Sampler Fails
Failures are guaranteed in a stress test. What matters is whether your test reacts sensibly and reports them clearly.
In a perfect world every request would succeed. In a real load test, requests fail for many reasons, and the way a failure is described tells you where to look. JMeter distinguishes several kinds, and learning to tell them apart saves hours.
| Kind of failure | What it looks like | Usual meaning |
|---|---|---|
| Client error | Response code in the 400 range | The request was wrong, such as a bad address, a missing token, or an expired session |
| Server error | Response code in the 500 range | The server failed while handling a valid request, often a sign of overload or a bug |
| Network error | “Non HTTP response code” followed by a connection or timeout message | The conversation never completed, because of refused connections, timeouts, or resets |
| Assertion failure | Normal response code but a failed check | The reply arrived but its content was not what you expected |
What should a thread do after a failure?
The thread group has a setting for the action to take after a sampler error. The options include continuing with the next sampler, starting the next loop of the thread, stopping that one thread, stopping the whole test gracefully, or stopping the whole test immediately. The right choice depends on the situation.
- Continue is good for independent steps, where one failure should not affect others, and it is the default.
- Start the next loop suits journeys where a failed step makes the remaining steps meaningless, such as a failed login.
- Stop the thread removes a broken virtual user without affecting the rest.
- Stop the test is suited to automated pipelines that must not waste time and money on a clearly broken run.
Imagine a cook following a recipe who discovers the eggs are rotten. They could keep cooking the rest of the dish, start over with fresh ingredients, or close the kitchen. The “action after error” setting is the rule you give the cook for each case.
Do not hide failures with retries
It can be tempting to add automatic retries so the report looks clean. Resist that. A retry that succeeds after a failure hides a real problem and adds extra load at the worst moment. If your real client retries, such as a mobile app that tries again after a timeout, then your test may include retry logic, but it should report the failed attempt clearly rather than burying it.
Expected failures
Sometimes a failure is the correct result. A test that checks how the system responds to a missing page should expect a 404 code. The Response Assertion has an option to ignore the status, so you can assert on the expected error message instead of having an expected error counted as a failure. Without this, your error percentage becomes meaningless because it mixes real problems with intended ones.
Keeping evidence
When something fails in a long run, you want to see what the server said, but saving every response body is too costly. JMeter has a setting that saves response data only for failed samples. This gives you evidence for investigation at a small cost. Combine it with clear labels and the thread name, and you can trace a failure back to the exact step and virtual user.
Agree on what an acceptable error rate is before the test starts. A target such as “fewer than one percent errors at the expected peak” makes the result a clear pass or fail instead of an endless debate.
PParameterization and Correlation
Two skills turn a recorded demo into a realistic test: giving every user different data, and passing live values from one request to the next.
Parameterization: different data for every user
Parameterization means replacing fixed values in a sampler with variables that change. Imagine a test where all one thousand virtual users log in as the same person, “alice”. The server would cache her data, lock her account, or treat the whole thing as unusual. Real traffic comes from many different people, so the test must too.
The usual tool is the CSV Data Set Config element. You prepare a plain text file of rows, for example usernames and passwords, and the element hands each thread a row. You then write the variable name inside the sampler using a dollar sign and curly braces, such as ${username}. At run time, JMeter swaps the placeholder for the real value. The same idea works for product identifiers, search words, addresses, and anything else that should vary.
Sharing mode
Decide whether the file is shared by all threads, per thread group, or per thread. Wrong choices cause duplicate users or skipped rows.
End of file behaviour
Choose whether to recycle the file, stop the thread, or stop the test when the data runs out. Running out silently is a classic mistake.
Enough rows
Have enough unique rows for your threads and loops. Reusing the same ten users for ten thousand iterations creates artificial locks and cache hits.
Built-in functions
JMeter can also generate random numbers, random strings, timestamps, and unique identifiers on the fly without any data file.
Correlation: passing live values forward
Many systems hand out values that are valid only once or only for a short time. A login response may contain a session token. A checkout page may contain a hidden one-time form value. An API may return the identifier of the order you just created. If your script replays a recorded token, the server rejects it because it expired long ago. Correlation means capturing such a value from a response and feeding it into later requests.
Find the dynamic value
Run the scenario twice and compare the responses. Anything that differs, such as tokens and identifiers, is a candidate.
Add an extractor to the sampler that returns it
Choose the JSON Extractor for JSON responses or a Regular Expression or Boundary Extractor for text. Give the result a variable name.
Use the variable in later samplers
Replace the old hard-coded value with the variable name wherever it appeared, including paths, headers, and bodies.
Verify with an assertion and a viewer
Confirm the later request really succeeded, not just that it returned a status of 200.
Think of a cloakroom ticket. You hand over your coat and receive a numbered ticket. You cannot photocopy someone else’s ticket from yesterday and expect it to work; you must use the ticket you were just given. Correlation is the habit of holding on to the fresh ticket and presenting it at the right moment.
If your test shows a perfect, fast response from every request but the data you expected never changes, or all requests quickly return error pages with a 200 code, suspect missing correlation. The server is politely rejecting stale tokens, and a missing assertion is hiding the problem.
Choosing the right extractor
Prefer the extractor that matches the shape of the data. A JSON Extractor with a path expression is precise and easy to read for JSON APIs. A CSS Selector Extractor is clean for picking an element from an HTML page. Regular expressions are flexible but easy to get wrong, and they are slower on very large pages. Always set a default value, such as “NOT_FOUND”, so a failed extraction is obvious in later requests instead of silently leaving an unexpanded placeholder.
Example: placing an order through an API
Step one creates a cart and the response includes a cart identifier. A JSON Extractor stores it. Step two adds items to that cart using the stored identifier. Step three checks out the same cart. If the extractor misses, all later steps fail, and the default value “NOT_FOUND” in the request path makes that failure easy to spot in the results.
RReading Results and Metrics
Samplers collect the facts. Listeners and reports turn them into answers.
A listener receives sample results and either shows them or saves them. The most important point about listeners is that they are tools for two very different situations: building a script, and measuring performance. Mixing them up is a frequent cause of bad test results.
Listeners for building a script
The View Results Tree shows each request and response in detail: headers, body, timings, and assertion results. It is an excellent debugging tool, and the one you will use most while writing a script. But it keeps a lot of data in memory and slows the test machine, so it must be disabled or removed during real load runs.
Listeners for measuring performance
| Listener | What it shows | Best used for |
|---|---|---|
| Summary Report | Per-sampler count, average, minimum, maximum, standard deviation, error percentage, throughput | A quick health check |
| Aggregate Report | Similar, plus median and the 90, 95, and 99 percent lines | Reporting percentile response times |
| Simple Data Writer | Writes raw results to a file with no screen display | Low-overhead recording for load runs |
| Backend Listener | Sends metrics to an external monitoring system while the test runs | Live dashboards for long tests |
Why averages lie
The average response time is the number everyone looks at first, and it is often misleading. Suppose ninety-nine requests take one second and one request takes a hundred seconds. The average is about two seconds, which sounds fine, yet one user had a terrible experience. Percentiles fix this. The 95th percentile is the time that 95 percent of requests beat. If it is two seconds, then only five in a hundred requests were slower. Percentiles describe what most users feel, including the unlucky ones, so serious reports show them next to the average.
The numbers that matter
- Throughput is how many requests complete per second or minute. It tells you how much work the system actually handled.
- Error rate is the percentage of failed samples. A rising error rate under growing load shows the breaking point.
- Response time percentiles show how fast the system felt to users at different levels of unluckiness.
- Active threads over time show how many virtual users were present, so you can connect results to load levels.
A useful rule is to read these numbers together. Throughput that climbs steadily with load, while response times stay flat, means the system is coping. Throughput that stops rising while response times climb means the system has hit a bottleneck. Errors starting to appear usually mean it has hit a hard limit such as a full connection pool or exhausted memory.
Run the test in non-GUI mode, which means starting it from the command line without the graphical window, saving results to a file. JMeter can then generate a complete HTML dashboard report from that file, with charts for response times over time, throughput, and errors. This is both lighter on the machine and easier to share.
Be careful with load-generator limits
The test machine can itself become the bottleneck. If its processor is at one hundred percent, or it is running out of memory, response times will look worse than they really are because the machine is too busy to measure properly. A trustworthy test always checks the health of the load generator, not just the system under test.
WWorkload Modelling: Users, Throughput, and Little’s Law
How many threads should you run? The answer is not a guess. It comes from a simple relationship between users, speed, and waiting.
Beginners often pick a thread count like one hundred or one thousand because it sounds right. Professionals work backwards from the traffic they expect. The central tool is a rule from queueing theory called Little’s Law. It says that the average number of users in a system equals the arrival rate multiplied by the average time each one spends there.
Think of a coffee shop. If a customer arrives every ten seconds and each stays for six minutes, then about thirty-six customers will be inside at any moment. You can work out the crowd just from how fast people arrive and how long they linger.
Applying it to a test
For a performance test, the “time in the system” for a virtual user is the response time plus the think time of each action. Suppose your business expects one hundred checkouts per second at peak. Suppose each virtual user completes one such action every ten seconds, made of two seconds of response time and eight seconds of think time. Then you need one hundred multiplied by ten, which is one thousand virtual users, to produce that rate. Using only one hundred threads would produce only ten actions per second and make a system look far healthier than it is.
Where the numbers come from
- Production analytics show real visits per hour, the busiest hour of the week, and the mix of pages and actions.
- Business forecasts estimate growth, marketing campaigns, and seasonal peaks, so you can test future traffic and not just today’s.
- Service targets tell you the response times that must hold at that load.
- Safety margin is added on top, commonly some extra percentage, so a good result today is still good when real traffic surprises you.
Controlling the rate directly
Sometimes you want to control the request rate rather than the user count. JMeter provides the Constant Throughput Timer, which slows threads down to hit a target number of requests per minute, and the Precise Throughput Timer, which aims to hit the target more evenly. Both only slow threads down; they cannot speed them up. If you do not give them enough threads, they cannot reach the target, so the thread count must be high enough to begin with. Plugins from the wider JMeter community add richer shapes of load, such as stepped increases or a defined pattern of ups and downs over time.
Shape of the test
Ramp-up
Users arrive gradually so caches and connection pools can warm up naturally.
Steady state
The target load is held long enough to collect stable measurements. This is the part you report.
Ramp-down
Users leave gradually, and you watch how the system recovers.
Write the workload model on one page: the target rate for each journey, the number of virtual users, the think times, and where each number came from. Anyone reviewing the results can then judge whether the test matched reality.
PPerformance and Scalability of the Test Itself
Generating load costs resources. Efficient samplers let one machine pretend to be many users.
Each virtual user is a Java thread, and every thread uses memory and processor time. JMeter by default starts with a modest memory limit, around one gigabyte in many versions, which is plenty for learning but too small for a large test. Teams tune the Java heap size, choose a recent Java release, and watch garbage collection pauses, because a long pause on the load generator looks exactly like a slow server.
Use non-GUI mode
The graphical window consumes memory and processing power. Real load runs should be started from the command line.
Minimize listeners
Remove View Results Tree and similar heavy viewers. Write results to a file and analyze them afterwards.
Use JSR223 with Groovy
Replace BeanShell and enable script compilation caching so scripting does not become a hotspot.
Keep responses small
Saving every response body is expensive. Save only what you need, and turn off embedded resource downloading when testing back-ends.
Mind the network
A load generator in the same fast network as the target may hide real-world latency, while one far away may be limited by its own bandwidth.
Set timeouts
Without connect and response timeouts, threads can hang forever on a stuck server and quietly reduce the load you think you are generating.
Distributed testing
When one machine cannot generate enough load, JMeter supports distributed testing. One machine acts as the controller. It sends the test plan to several remote machines, each called an engine, and all of them run the plan at the same time. The controller then gathers the results. Each engine runs the full thread count you specify, so ten engines with one hundred threads each produce one thousand users. Everything must be kept in sync: the same JMeter version, the same data files, and a network where the machines can reach each other.
Benefits of distribution
- Larger load than a single machine can produce
- Load can come from different locations or networks
- Each engine’s resources are used in parallel
Costs of distribution
- More machines to set up, secure, and keep identical
- Data files must be placed on every engine
- The controller can become a bottleneck when collecting results
Cloud and container deployment
Many teams now run JMeter engines in containers or on cloud virtual machines. They create them when a test starts and delete them afterwards, which makes large tests affordable. The generators should sit in a region and network that fits the question you are asking. If real customers come from several continents, generators in several regions will give you more honest numbers than one big machine next door to the server.
Think about the load model
There are two ways to describe load. A closed model has a fixed number of users who each wait for a response before acting again, which is how a normal thread group works. An open model has requests arriving at a certain rate, regardless of how fast the system answers, which is how the public internet behaves. When a server slows down in a closed model, the users naturally slow down too and the test eases off. In an open model, requests keep coming and a slow system gets buried. Neither is wrong, but you should pick the one that matches the question you are asking, and be aware of the difference when you read the results.
Ramp up gradually. Starting thousands of threads in one second creates a startup spike that tells you about connection storms, not about steady behaviour. Give the system time to warm its caches and connection pools, and measure the steady stretch separately from the warm-up.
SSecurity Considerations
A load test is real traffic with real credentials. Treat the test plan like any other piece of sensitive software.
Test only what you are allowed to test
The first rule of load testing is permission. Run tests only against systems you own or have explicit written approval to test. A big load test aimed at a system without permission looks identical to an attack, and may break the law or the terms of a cloud provider or a third-party service. Even inside a company, inform the teams that operate the target, the network, and any shared services, so they do not raise an emergency when traffic spikes.
Protect credentials and secrets
Test plans often contain usernames, passwords, tokens, and database credentials. These are easy to leak. A test plan saved in a shared folder or a version control system exposes whatever is stored in it. Keep secrets out of the plan when possible. Supply them at run time as properties, load them from a protected data file, or use a secrets manager. Never commit real production passwords into a repository, and use dedicated test accounts with limited rights.
Result files and logs can contain sensitive data too, such as tokens inside response bodies and headers. Treat saved results with the same care as the credentials that produced them, and delete them when they are no longer needed.
Use test data, not real customer data
Never feed real customer information into a load test. Use generated or anonymized data. Not only is this safer, it also avoids accidentally sending real emails, text messages, or payments. Many a team has learned the hard way that a load test against a live email system can send a flood of real messages to real people.
Secure the connections
For HTTPS, JMeter must trust the server’s certificate. Resist the temptation to switch off certificate checks in a shared environment. If the test uses client certificates, store them securely. In distributed mode, communication between the controller and the engines should happen on a private network, because by default these links are meant for trusted environments. Secure them with the available encryption settings and firewall rules, and never expose the engines to the public internet.
Be careful with powerful samplers
Some samplers can run commands or arbitrary scripts on the load machine. The OS Process Sampler and script samplers fall in this category. A test plan is, in effect, a program. Opening a plan from an untrusted source is similar to running an unknown program, so review plans from outside sources before opening them, and restrict who can change plans that run on shared infrastructure.
Before the test
Get approval, notify the owners of the target and the network, use test accounts and generated data, and keep secrets out of the saved plan.
During the test
Watch for signs the test is harming shared systems, and keep a clear way to stop everything quickly.
After the test
Clean up created data, remove the generators, and securely store or delete result files.
MMonitoring, Logging, and Metrics
A good test watches two things at once: what the samplers see from outside, and what the system and the load generator feel inside.
Sample results show how long things took from the user’s point of view. They cannot, on their own, explain why something was slow. For that, you combine them with monitoring of the system under test. If response times rise at the same moment the database’s processor reaches its limit, you have found a likely cause. If they rise while the application server’s memory fills up, the story is different. Performance work is largely the skill of lining these views up in time.
flowchart LR
S["Samplers in JMeter"] --> JTL["Results File"]
S --> BL["Backend Listener"]
BL --> TS["Time-Series Database"]
TS --> G["Dashboard"]
SYS["System Under Test"] --> MON["Server Monitoring: CPU, memory, disk, network"]
MON --> TS
JTL --> HTML["HTML Report"]
LG["Load Generator Health"] --> MON
Where the data goes
The default way to record results is a results file, often with the extension JTL. It is a table in which each row is one sample result. You choose which columns to save, such as timestamp, elapsed time, label, response code, success flag, bytes, and thread counts. Saving more columns gives richer analysis but bigger files, so decide deliberately. Saving full response bodies for every request is almost never a good idea in a large test.
The Backend Listener streams metrics to an external system while the test runs. Common targets are time-series databases that feed live dashboards. With it, a team can watch response times, error rates, and active users move on a screen during a long soak test, and stop early if something goes badly wrong.
The JMeter log
JMeter also writes a log file that records startup messages, warnings, and errors in the tool itself, such as a missing data file or a script that failed to compile. When a sampler behaves strangely or a test plan stops early, this log is the first place to look. A rising count of log errors during a run is a signal worth taking seriously.
What to monitor on the other side
Processor and memory
Show when servers are overloaded or leaking memory over a long run.
Disk and database
Reveal slow queries, lock waits, and full disks.
Bandwidth and connections
Show saturated links and exhausted connection limits.
Load machine health
Confirms the test tool itself was not the bottleneck.
Use the same clock on every machine, and label every test run with a clear name and time. When you later compare two runs, you will be glad you can match each chart to the exact test it came from.
AAdvantages, Disadvantages, and Trade-offs
JMeter’s sampler model is powerful, but every design choice has a price.
Strengths
- Free and open source, with a large community and many plugins
- Many protocols supported through one consistent sampler model
- Runs on any system that supports Java
- Rich ecosystem of controllers, timers, extractors, and assertions
- Works in non-GUI mode for automation and pipelines
- Scales out through distributed testing
Limitations
- One Java thread per virtual user uses a lot of memory at very high scale
- The graphical interface is clumsy for large plans
- Test plans are stored as large files that are hard to review in version control
- It does not run real browser scripts, so client-side rendering time is not measured
- Correlating dynamic values takes careful manual work
Protocol-level versus browser-level testing
A key trade-off is that JMeter works at the protocol level. Its samplers send requests and read responses, but they do not draw pages or run the page’s scripts. This is a strength for load generation, because it is lightweight and one machine can simulate many users. It is a limit for measuring what a person actually sees, because rendering time in the browser is invisible. Teams often combine the two: JMeter for heavy load on the back-end, and a small number of real browsers to measure user-perceived page speed while that load runs.
Realism versus effort
The more realistic a test, the more work it takes. Full user journeys with correlation, varied data, and proper think time are expensive to build and maintain. A simple single-request test is cheap but may miss the problems that matter. A sensible approach is to begin simple, learn what the system does, and add realism where the results show it is needed.
Be as realistic as the question demands, and no more. If you only need to know whether a single API can handle five hundred requests per second, one sampler and a few assertions may be enough.
DDesign Patterns and Anti-patterns
Patterns are habits that keep test plans healthy. Anti-patterns are habits that quietly ruin results.
Helpful patterns
Defaults at the top
Put server details in HTTP Request Defaults and change environments in one place.
Transactions for business steps
Wrap related samplers so reports speak the language of the business.
Setup, test, teardown
Create data in setUp, run the test, clean up in tearDown, so each run starts and ends clean.
Reusable fragments
Put common steps in a Test Fragment and call them from several places so changes are made once.
Properties for settings
Pass thread counts, durations, and environments as command-line properties, so the same plan serves smoke, load, and stress runs.
Debug, then disable
Use a Debug Sampler and a results tree while building, and switch them off before scaling.
Anti-patterns to avoid
The pattern
Running the load test from inside the graphical interface with a results tree open.
Why it hurts
The window and the in-memory result storage slow the machine, so the numbers measure the test tool as much as the target.
Better approach
Build and debug in the GUI, then run the real test in non-GUI mode and analyze results afterwards.
The pattern
No think time, so every virtual user fires requests back to back.
Why it hurts
A small number of threads creates an unrealistic request rate. The test measures a machine-gun, not a crowd of people.
Better approach
Add realistic timers, and when a target request rate matters, use a rate-controlling timer on purpose.
The pattern
Trusting the status code alone and having no content assertions.
Why it hurts
Error pages that return a 200 code count as successes, so the report looks healthy while users are actually failing.
Better approach
Add assertions on the business content of the response, and check a sample of results by eye.
The pattern
Replaying a recording without cleaning it.
Why it hurts
Recorded scripts include hard-coded tokens, third-party calls, and raw numbers that fail or distort results when replayed at scale.
Better approach
Treat a recording as a first draft. Remove noise, add variables and extractors, and rename samplers clearly.
The pattern
Using the same single user account for every virtual user.
Why it hurts
It causes lock contention, cache hits, and session conflicts that real traffic would never create.
Better approach
Provide a data file of many unique accounts and make sure each thread gets its own.
BBest Practices and Common Mistakes
A short, practical checklist to carry into your next test.
Start with a clear question
Decide what you want to learn: the maximum users, the response time at expected load, or the behaviour over many hours. The question shapes the whole plan.
Run a single user first
Prove that one virtual user can complete the journey correctly before adding any load.
Name and organize everything
Use clear, numbered names, group steps in transactions, and add comments for non-obvious choices.
Add assertions and timeouts
Check the content, and set connect and response timeouts so stuck requests cannot hide.
Ramp up and warm up
Increase load gradually, and analyze the steady period separately from the start.
Repeat the test
One run is an anecdote. Two or three consistent runs are evidence. Change only one thing between runs.
Keep plans under version control
Store plans and data files in a repository, review changes, and tag the version used for each important test.
Common mistakes in one list
- Testing on a tiny environment and expecting the numbers to predict production
- Forgetting to clear the cache or cookies between iterations, so every user looks like a returning visitor
- Loading third-party content that you do not own
- Reading only the average and ignoring percentiles and errors
- Leaving a Debug Sampler or a results tree enabled during the real run
- Ignoring the health of the load generator itself
- Declaring a pass without checking that the responses were correct
Before any important test, run a short “smoke” test at tiny load. It costs a few minutes and catches broken scripts, missing data files, and wrong addresses before they waste hours of a big run.
OOrganizing Large Test Plans and Automating Them
A test that only one person can run, on one laptop, once, is a demo. A real test is repeatable, reviewable, and automatic.
As a project grows, a test plan can contain hundreds of samplers. Without structure it becomes unreadable. JMeter offers several tools to keep plans manageable, and the habits around them matter as much as the tools.
Reuse instead of copy
A Test Fragment is a branch of the tree that does not run by itself. Other parts of the plan call it through a Module Controller, which runs a chosen fragment inside the current plan. An Include Controller does the same with a fragment stored in a separate file. This lets several journeys share a common step, such as login, written once. When the login changes, you fix it in one place rather than in twenty copies.
flowchart TD
TP["Main Test Plan"] --> TG1["Thread Group: Shoppers"]
TP --> TG2["Thread Group: Admins"]
TG1 --> MC1["Module Controller: Login"]
TG2 --> MC2["Module Controller: Login"]
MC1 --> FR["Test Fragment: Login steps"]
MC2 --> FR
FR --> L1["Sampler: Open Login Page"]
FR --> L2["Sampler: Submit Credentials"]
Naming and layout conventions
Agree on a naming style and stick to it. Samplers might begin with a step number, then the business action, such as “03 Search Product”. Variables use a consistent style. Folders hold data files, plans, and result reports in predictable places. These small habits mean that a new teammate, or you six months later, can open the plan and understand it quickly.
Settings outside the plan
Hard-coding thread counts, durations, and server names inside the plan forces you to edit it for every environment. Instead, read them from properties passed when the test starts. Then one plan serves a quick smoke run, a full load run, and a long soak run, and the settings used are recorded with each run. Similarly, keep test data in separate files rather than inside the plan.
Version control and review
A JMeter plan is saved as a text file in a markup format, which can be stored in a repository like code. Large plans produce noisy differences, so make small, focused changes and describe them clearly when committing. Review changes with a second person, especially script samplers and anything that touches credentials. Tag the plan version used for every important report so results can be reproduced.
Running in an automated pipeline
Modern teams run performance tests automatically as part of their delivery pipeline. A typical flow starts the test in non-GUI mode, collects the results file, produces a report, and then applies quality gates: the pipeline fails if the error rate or the 95th percentile exceeds an agreed limit. Some teams wrap JMeter with helper tools that describe tests in a simpler configuration format and add easy reporting, and others use build-tool plugins. Whatever the choice, the principle is the same: performance becomes a check that runs on every important change, not a rare event before a big launch.
Benefits of automation
- Regressions in speed are caught early, close to the change that caused them
- Results are consistent and comparable between runs
- No one needs to remember to run the test
Things to watch
- Shared pipeline machines may be too weak or noisy for reliable load
- Small tests in a pipeline cannot replace a full-scale test before a major event
- Thresholds must be revisited as the system and expectations change
Keep a baseline
Save the results of a known-good run as a baseline. Later runs are compared against it, so a slow change shows up as a number instead of a feeling. Without a baseline, you can only say that a result “looks okay”. With a baseline, you can say it is eleven percent slower than last month.
RReal-World Scenarios
The sampler model fits many situations. Here are four typical ones, described in general terms so you can adapt them.
A flash sale on an online store
An e-commerce team expects a sudden jump in visitors when a discount starts. They build a plan with browse, search, and checkout journeys, use throughput controllers to match the real mix of visitors and buyers, and use a synchronizing timer to simulate many users arriving at the exact start moment. They watch the 95th percentile of the checkout transaction and the error rate as load rises, and they look at the database’s connection pool in parallel.
An API behind a mobile app
A mobile team wants to know how many users their back-end can support. They use HTTP samplers with JSON bodies, a header manager for the authorization token, a JSON Extractor to carry identifiers between steps, and JSON assertions to verify business results. Because a mobile app has no embedded web resources, they keep embedded downloading off.
A payroll or reporting run
A finance system runs a heavy batch job at month-end. The team uses JDBC samplers to test the most expensive queries in isolation, with a few threads, and compares response times before and after a database index is added. This shows the effect of a single change without needing the whole application.
A messaging pipeline
A company that processes orders through a queue wants to be sure a busy day will not create a backlog. They use JMS publishers to send messages at increasing rates and a subscriber to measure the time until messages come out the other side. The key metric is end-to-end delay, and the signal of trouble is a queue that keeps growing.
Soak, stress, and spike tests
The same samplers can serve different test types. A load test applies the expected traffic and checks that targets are met. A stress test keeps increasing load until the system breaks, to learn where and how it fails. A spike test hits the system with a sudden surge to see whether it recovers. A soak test runs moderate load for many hours to expose memory leaks and slow decay. Only the thread group settings and the timers change; the samplers stay the same.
FFrequently Asked Questions
Quick answers to the questions beginners ask most often.
SSummary and Key Takeaways
A sampler is the part of JMeter that does the real work. It sends one request, waits for the reply, and records what happened. Everything around it, from controllers and timers to extractors, assertions, and listeners, exists to make that single act realistic, correct, and measurable. If you remember only one idea from this guide, remember that a performance test is only as trustworthy as its samplers and the checks wrapped around them.
Key Takeaways
- A sampler sends a request and records a sample result. It is the only element that produces timing and size measurements.
- Position and scope matter. Where an element sits in the tree decides which samplers it affects and when it runs.
- Know the execution order. Config elements, pre-processors, timers, the sampler, post-processors, assertions, then listeners.
- Read the right numbers. Prefer percentiles, throughput, and error rate over the average, and always check content with assertions.
- Parameterize and correlate. Give every user their own data and pass fresh tokens forward, or the test will not resemble real traffic.
- Protect the test itself. Use non-GUI mode, minimal listeners, Groovy scripts, timeouts, and distributed engines so the load generator never becomes the bottleneck.
- Test responsibly. Get permission, use test data, guard credentials and result files, and review plans from unknown sources before opening them.