Recording & cURL Import
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. |
The reference material consulted while preparing these pages is listed in the section bibliography.
This page was written against Apache JMeter 5.6.3 (latest release on 2026-10-14; it runs on Java 8 and later, Java 17
or later is recommended), with the differences planned for JMeter 6.0 noted where they matter. JMeter 6.0 is not
released: the planned changes come from xdocs/changes.xml on the master branch, need Java 17 or later (Java 21
recommended) and are described as planned, not as shipped behaviour. HTTPS recording needs the keytool program of a
JDK on the recording machine. No plugin is needed: the recorder, the Recording template and Import from cURL ship
with core JMeter. The books on the market target JMeter 2.11 to 3.0: they record through the WorkBench, which was
removed in 4.0, and the Cookbook also converts traffic with third-party tools (a browser extension and an online HAR
converter) that are not covered here. Every field name, property and default on this page is taken from the official
component reference, properties reference, the recorder tutorial and the cURL page, and where the documentation is
silent or out of date, from the 5.6.3 source code, which is said explicitly.
Writing a test plan by hand is the cleanest way to test a JSON API whose contract is known, and
HTTP Requests does exactly that for the Bookshelf API. Two situations call for
capturing real traffic instead: an application whose front end issues many calls in an order nobody has written down,
and a single request someone already has as a curl command (from a bug report, from API documentation or from the
browser’s developer tools). JMeter has one tool for each. The HTTP(S) Test Script Recorder is a proxy that turns
everything a browser (or any HTTP client) sends through it into HTTP Request samplers. Tools → Import from cURL turns
one or more curl command lines into samplers. Both produce a rough draft: a recording is never a load test until it
has been cleaned up, variabilised and correlated, which is the second half of this page.
The running example is the Bookshelf API journey used throughout the section: log in with
POST /api/auth/login, search with GET /api/books?query=, view a book with GET /api/books/42, create a loan with
POST /api/loans and view it with GET /api/loans/7. The API runs locally on port 8080 (started with Docker Compose,
see Compose for Local Development); the requests are recorded
from the Bookshelf web front end that calls it.
Two ways to capture traffic
| HTTP(S) Test Script Recorder | Import from cURL | |
|---|---|---|
What it captures |
Every request a client sends through the proxy while it runs: pages, API calls, redirects, embedded resources. |
Exactly the requests written as |
Typical source |
A person clicking through the application in a browser; a mobile emulator, a CLI or a service configured to use the proxy. |
Browser developer tools (Copy as cURL), API documentation, a colleague’s reproduction of a bug. |
HTTPS |
Needs the JMeter root CA certificate trusted by the client (JMeter decrypts and re-encrypts the traffic). |
Nothing to install: JMeter only reads the command line. |
Output |
Samplers in the target controller, optionally grouped per click into Transaction Controllers, each with its own Header Manager; an Authorization Manager when Basic or Digest credentials are seen. |
An HTTP Request with a Header Manager, plus a Cookie, Authorization or DNS Cache Manager when the command needs one; a complete plan with a parameterised Thread Group when the plan is empty. |
Cleanup needed |
A lot: noise, hard-coded hosts, duplicated headers, recorded tokens and IDs, recorded pauses. |
Some: hard-coded host, tokens, cookies and browser headers. |
Runs during the load test? |
No. It is a non-test element used in the GUI only. |
No. It is a GUI menu action. |
Both tools need the GUI. That fits the rule the manual gives for the GUI in general: build and debug the plan in the GUI, run the load in CLI mode (GUI & Test Plan Workflow).
How the recorder works
The recorder is implemented as an HTTP(S) proxy server. The browser is configured to send all HTTP and HTTPS requests to it; the recorder forwards each request to the real server with the HTTP sampler, returns the response to the browser, and at the same time creates a sampler that would send the same request, storing it in the test plan "as you go", so the samples can be watched while they are made. For HTTPS it has to read the encrypted traffic, so it acts as a deliberate man in the middle: it terminates the browser’s TLS connection with a certificate it generates for the target host, signed by its own root CA, and opens a separate TLS connection to the real server.
The order of the steps matters for troubleshooting:
-
Certificate problems happen before any sampler exists. If the browser rejects the generated certificate, the request never reaches step 5 and nothing is recorded. The browser shows an error and the JMeter log may mention
unknown_ca. -
Filtering happens after the response. Every request passes through the proxy and reaches the server, even the ones the filters exclude; the filters only decide what is stored. Excluding analytics or static files does not stop the browser from loading them.
-
Plain HTTP skips the certificate part. The local Bookshelf API on plain HTTP port 8080 needs no certificate at all; the CA steps are needed only for HTTPS sites.
Starting from the Recording template
The recorder can be added by hand (Add → Non-Test Elements → HTTP(S) Test Script Recorder, as a child of the Test Plan), but it needs a Thread Group with a Recording Controller to store anything. The fastest start is the Recording template, which the official step-by-step tutorial also uses.
-
Start JMeter in GUI mode (
bin/jmeteron Linux or macOS,bin\jmeterw.baton Windows) on a machine with a JDK. -
Choose File → Templates… (or the Templates toolbar icon) and create a plan from the Recording template.
-
The template has three parameters. Fill them in and confirm with Validate:
Parameter Default Used for recordingOutputFilerecording.xmlFile name of the View Results Tree placed inside the recorder, which saves every recorded request and response (headers and bodies included) to that file.
hostToRecordwww.example.comValue of the User Defined Variable
hostand of Server Name or IP in the HTTP Request Defaults.schemeToRecordhttpsValue of the User Defined Variable
schemeand of Protocol in the HTTP Request Defaults. -
Look at the generated plan, then select the HTTP(S) Test Script Recorder and click Start. On the first start JMeter creates the keystore and the root CA certificate (the GUI is unresponsive for a moment) and shows an information dialog with the certificate details and its validity; click OK.
-
Configure the browser to use the proxy (next section), then browse.
For the Bookshelf API the values are hostToRecord = the recording machine’s address (for example 192.168.1.20; see
the loopback note below) and schemeToRecord = http. The template creates this tree:
Test Plan
├── User Defined Variables host = 192.168.1.20, scheme = http
├── HTTP Request Defaults Server Name or IP = 192.168.1.20, Protocol = http
├── HTTP Cookie Manager
├── Thread Group 1 thread, ramp-up 1 s, 1 loop
│ └── Recording Controller target of the recorder
├── View Results Tree
└── HTTP(S) Test Script Recorder port 8888, saved disabled
└── View Results Tree writes recordingOutputFile
Every part of it is deliberate:
-
User Defined Variables at Test Plan level make the recorder replace the recorded host and scheme with
${host}and${scheme}wherever they appear (see HTTP Request Defaults and variable replacement). -
HTTP Request Defaults with the same host and scheme make the recorded samplers leave Server Name or IP and Protocol empty, so they inherit them from the Defaults at replay.
-
HTTP Cookie Manager is needed at replay: the recorder never stores cookies in the plan.
-
The Recording Controller under the Thread Group is where Target Controller: Use Recording Controller stores the samplers. It behaves like a Simple Controller at run time (Recording Controller).
-
The recorder element is saved disabled (
enabled="false"in the template file). It never takes part in a test run anyway; it is driven only by its own Start and Stop buttons. -
The View Results Tree inside the recorder shows the server’s responses while recording. The manual’s answer to "how can I record the server’s responses too?" is exactly this listener as a child of the recorder.
The recorder element saved by the template looks like this (exclude list shortened):
<ProxyControl guiclass="ProxyControlGui" testclass="ProxyControl" testname="HTTP(S) Test Script Recorder" enabled="false">
<stringProp name="ProxyControlGui.port">8888</stringProp>
<collectionProp name="ProxyControlGui.exclude_list">
<stringProp name="1179605444">(?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|eot|otf|ttf|mp4|woff|woff2)</stringProp>
<stringProp name="305776760">(?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|eot|otf|ttf|mp4|woff|woff2)[\?;].*</stringProp>
<stringProp name="-48692778">www\.google-analytics\.com.*</stringProp>
</collectionProp>
<collectionProp name="ProxyControlGui.include_list"/>
<boolProp name="ProxyControlGui.capture_http_headers">true</boolProp>
<intProp name="ProxyControlGui.grouping_mode">4</intProp>
<boolProp name="ProxyControlGui.add_assertion">false</boolProp>
<stringProp name="ProxyControlGui.sampler_type_name"></stringProp>
<boolProp name="ProxyControlGui.sampler_redirect_automatically">false</boolProp>
<boolProp name="ProxyControlGui.sampler_follow_redirects">true</boolProp>
<boolProp name="ProxyControlGui.use_keepalive">true</boolProp>
<boolProp name="ProxyControlGui.sampler_download_images">false</boolProp>
<boolProp name="ProxyControlGui.regex_match">true</boolProp>
<stringProp name="ProxyControlGui.content_type_include"></stringProp>
<stringProp name="ProxyControlGui.content_type_exclude"></stringProp>
<boolProp name="ProxyControlGui.notify_child_sl_filtered">false</boolProp>
<stringProp name="ProxyControlGui.proxy_prefix_http_sampler_name"></stringProp>
<intProp name="ProxyControlGui.proxy_http_sampler_naming_mode">1</intProp>
<stringProp name="ProxyControlGui.proxy_pause_http_sampler"></stringProp>
</ProxyControl>
grouping_mode 4 is Put each group in a new transaction controller and proxy_http_sampler_naming_mode 1 is
Transaction name, so the template already records one Transaction Controller per click.
The Recording with Think Time template is the same plan plus a timer inside the recorder; it is described under Recording think time.
Pointing the browser at the recorder
The recorder listens on the configured port (8888 by default) of the JMeter machine. In the browser’s proxy settings
choose a manual configuration with the JMeter host (localhost when the browser runs on the same machine) and port
8888, and use the proxy for all protocols, so that HTTPS goes through it too. The tutorial describes the older Firefox
path (Edit → Preferences → Advanced → Network → Settings → Manual proxy configuration, then Use this proxy server
for all protocols); in current Firefox releases the same dialog is under Settings → General → Network Settings →
Settings….
Practical rules from the manual:
-
Use a private window or a dedicated profile. It starts with no cookies and no cache, so the first visit records the real login and the real downloads, and certificate overrides are not stored permanently. A separate Firefox profile also keeps the JMeter CA and the proxy setting away from everyday browsing.
-
Only HTTP and HTTPS. Do not send FTP or other protocols through the recorder; JMeter cannot handle them.
-
A corporate proxy needs
-Hand-P. If the browser normally reaches the network through a proxy, start JMeter with-Hand-Pso the recorder can forward traffic, and use the same options when replaying the plan (Outbound proxy). -
Loopback addresses may bypass the proxy. Some browsers never send requests for
localhostor127.0.0.1through a proxy, so recordinghttp://localhost:8080silently records nothing. The manual’s advice is to browse the application with the machine’s non-loopback host name or IP address instead (for Bookshelf,http://192.168.1.20:8080), which is why the template parameter above uses that address. The recorder later turns it into${host}anyway. -
Check that the browser really uses the proxy. Stop the recorder and reload: if the page still loads, the browser is not sending requests through JMeter.
HTTPS recording and the JMeter root CA
What JMeter generates
To intercept HTTPS, JMeter "has to pretend to be the target server". Since 2.10, on Java 8 or later, it works in dynamic mode:
-
On the first Start, JMeter creates a keystore,
proxyserver.jks, in the JMeterbindirectory, with a root CA key pair. Unless a keystore and alias are supplied, it does this with the JDK’skeytool, so a JRE withoutkeytoolcannot record HTTPS. Ifkeytoolis not found in the standard places, set the system propertykeytool.directoryinbin/system.properties. -
It exports the root CA certificate as
ApacheJMeterTemporaryRootCA.crt(andApacheJMeterTemporaryRootCA.usrfor Opera) into thebindirectory, where the tutorial and the start-up dialog say to find it. The component reference says "the current launch directory"; in 5.6.3 the keystore and the exported files followproxy.cert.directory, which defaults tobin. -
For every HTTPS host the browser opens, it generates a certificate for that host name, signed by the root CA. A browser that trusts the root CA therefore accepts every generated certificate, including those of embedded resources on other hosts (a CDN, an identity provider), without a prompt per host.
-
The certificates are valid for
proxy.cert.validitydays, 7 by default, and the keystore and key passwords are generated at random and kept in the user’s Java preferences.
The CA certificate’s subject is _ JMeter Root CA for recording (INSTALL ONLY IF IT S YOURS). Its name is a warning:
once a browser trusts that CA, anyone who obtains the keystore and its password can mint certificates the browser
accepts for any site, without a warning, until the certificate expires or is removed. The short validity and the
random passwords exist for that reason, and the manual asks that only trusted users have access to the machine holding
the keystore.
Installing the certificate
Install ApacheJMeterTemporaryRootCA.crt as a trusted certificate authority (not as a site exception), after
comparing its details with the dialog JMeter showed on Start:
| Client | Steps |
|---|---|
Firefox |
Firefox has its own certificate store. Open the certificate manager (Settings → Privacy & Security → Certificates →
View Certificates…; the manual describes the older Tools / Options / Advanced / Certificates path), go to
Authorities, click Import…, select |
Chrome, Edge (Windows) |
These use the operating system store. Open |
Opera |
Tools / Preferences / Advanced / Security → Manage Certificates… → Intermediate → Import…, then select
|
Other browsers and operating systems |
Import the |
Remove it when recording is over. A trusted JMeter CA left in a browser or an operating system store weakens TLS for every site that browser visits. Prefer a throw-away browser profile, delete the certificate from the store at the end of the session, and never install it on a shared machine. The certificate shown by the browser for a recorded site will be issued by the JMeter CA; that is expected while recording and a red flag at any other time.
Validity and regenerating proxyserver.jks
After 7 days the generated certificates expire and the browser starts rejecting the recorder again. The start-up dialog warns about it: if the certificate it shows is newer than the one installed, remove the old one from the browser and install the new one.
To start again from a clean CA (expired certificate, a keystore copied from another machine, a suspicion that the keystore leaked):
# 1. Stop JMeter, then delete the keystore and the exported certificates
cd "$JMETER_HOME/bin"
rm proxyserver.jks ApacheJMeterTemporaryRootCA.crt ApacheJMeterTemporaryRootCA.usr
# 2. Start JMeter, open the plan, click Start on the recorder:
# a new keystore, a new root CA and a new ApacheJMeterTemporaryRootCA.crt are generated
ls -l proxyserver.jks ApacheJMeterTemporaryRootCA.*
The listing shows the three files again, with the time of the new Start. Then remove the old CA from every browser that trusted it and install the new one.
A longer validity is possible but trades convenience for exposure. Set it in user.properties before the keystore is
generated (delete proxyserver.jks after changing it so the new value is used):
# user.properties on the recording machine
# Validity in days of the certificates JMeter generates for recording (default 7)
proxy.cert.validity=14
Certificate properties
All of these are JMeter properties (user.properties, or -J on the command line). They are read when the recorder
classes load, so restart JMeter after changing them.
| Property | Default | Meaning |
|---|---|---|
|
JMeter |
Directory of the keystore. |
|
|
Keystore file name. Deleting this file forces regeneration of the keystore and of the exported certificates. |
|
|
Keystore type. Ignored when JMeter generates its own certificate. |
|
|
Keystore password. Ignored when JMeter generates its own certificate (it then uses a random password). |
|
|
Key password. Ignored when JMeter generates its own certificate. |
|
|
Key manager factory algorithm. Ignored when JMeter generates its own certificate. |
|
empty |
Alias of an existing key in a keystore you supply. When set, JMeter does not generate any certificate and uses that one key for every host (the pre-2.10 behaviour, so the browser prompts for each host). |
|
|
Validity in days of the certificates JMeter generates. |
|
|
Generate one certificate per host. If |
|
|
Protocol of the |
HTTPS Domains
HTTPS Domains (in Global Settings, next to Port) is a comma-separated list of host names or wildcard domains for
which certificates are generated in advance, when the recorder starts, instead of on the first request. For example
*.bookshelf.example.com,auth.example.com. A wildcard applies to one level only: abc.subdomain.example.com matches
*.subdomain.example.com but not *.example.com. In dynamic mode the field is optional, because certificates are
generated per host on demand anyway; it is useful to avoid a pause on the first request to each host.
The unknown_ca error and other certificate symptoms
| Symptom | Cause | Fix |
|---|---|---|
Browser error page for every HTTPS site; |
The browser has not accepted the JMeter proxy certificate: the root CA is not installed, or it was installed in a different store from the one the browser reads. |
Install |
It worked last week, now every HTTPS page fails |
The 7-day validity expired, or |
Install the new |
The main page loads but images, scripts or API calls on another host fail |
The browser prompts only for the main URL, not for embedded resources; this happens when dynamic mode is off
( |
Use dynamic mode with the root CA installed, or browse to each secure host once to accept it; |
The browser refuses to load the page at all, with no way to accept the certificate |
The browser has already registered a validated certificate for that domain and treats JMeter as a security breach. |
Remove that trusted certificate from the browser’s store (for example by using a fresh profile). |
Error on Start that |
JMeter runs on a JRE without |
Run JMeter on a JDK, or set |
Every recorder field
The recorder panel has a State area (Start, Stop, Restart), a Global Settings area (Port, HTTPS Domains) and two
tabs: Test Plan Creation and Requests Filtering. The .jmx property names come from the 5.6.3 source and the
Recording template.
State and Global Settings
| Field | Meaning | JMX property |
|---|---|---|
Start |
Starts the proxy (generating certificates if needed). The console shows "Proxy up and running!" when it accepts requests. If the port is taken the GUI reports "Could not create script recorder - port in use. Choose another port." |
(action) |
Stop |
Stops the proxy. Reset the browser’s proxy settings afterwards. |
(action) |
Restart |
Stops and starts the proxy; needed after changing include or exclude patterns while recording. |
(action) |
Port |
Port the proxy listens on. Default 8888 (since 3.0, as in the Recording template). Required. |
|
HTTPS Domains |
Host names or one-level wildcards to pre-generate certificates for (see above). |
|
Test Plan Creation tab
| Field | Meaning | JMX property |
|---|---|---|
Target Controller |
Where the samplers are stored. Use Recording Controller (the default) means the first Recording Controller in the tree; if there is none, Start fails with the message that a Recording Controller must be added under a Thread Group. Any other controller in the tree can be chosen instead. |
(selected in the GUI) |
Grouping |
How requests of one "click" (requests without a significant gap) are grouped. See Grouping and transaction naming. |
|
Capture HTTP Headers |
Adds a Header Manager to each recorded sampler with the request headers, minus Cookie and the headers in
|
|
Add Assertions |
Adds an empty Response Assertion to each sampler, as a placeholder to fill in. |
|
Regex Matching |
Treats the values of User Defined Variables as regular expressions, wrapped in word boundaries, when replacing them with variable references. See HTTP Request Defaults and variable replacement. |
|
Transaction name (Prefix/Transaction name in the manual) |
Text used to name samplers and grouping controllers, according to the naming scheme. Can be changed while recording. |
|
Naming scheme |
Prefix, Transaction name, Suffix or Use format string. See Naming schemes. |
|
Naming format |
Format used when the scheme is Use format string. |
|
Counter start value, Set counter |
Resets the request counter used in names. The next sample increments first, so set 0 to start at 1. |
(action) |
Create new transaction after request (ms) |
Inactivity gap that starts a new group; empty means the property |
|
Recording’s default encoding |
Encoding used to decode recorded parameters; UTF-8 by default since 5.6.1, so non-ASCII arguments are recorded readable instead of percent-encoded. |
|
Retrieve All Embedded Resources |
Sets that option in the generated samplers (off by default). |
|
Redirect Automatically |
Sets that option in the generated samplers (off by default). |
|
Follow Redirects |
Sets that option in the generated samplers (on by default). See Redirects, file uploads and binary bodies. |
|
Use KeepAlive |
Sets that option in the generated samplers (on by default). |
|
Type |
Implementation of the generated samplers: |
|
Detect GraphQL Request (GraphQL HTTP Sampler settings) |
When on (the default), a POST with a GraphQL content type or a GET with GraphQL query parameters is recorded as a GraphQL HTTP Request instead of a plain HTTP Request. Present in 5.6.3; not in the component reference table. |
|
Requests Filtering tab
| Field | Meaning | JMX property |
|---|---|---|
Content-type filter: Include, Exclude |
Regular expressions checked to see whether they are contained in the response content type (they need not match the whole value). Include is checked first, then Exclude. Filtered samples are not stored. This filters on the content type of the response. |
|
URL Patterns to Include |
Regular expressions matched against the full request URL (see Include and exclude patterns). If there is at least one, only requests matching one of them are recorded. Buttons: Add, Delete, Add from Clipboard. |
|
URL Patterns to Exclude |
Requests matching any of these are not recorded. Buttons: Add, Delete, Add from Clipboard, Add suggested Excludes
(the patterns in |
|
Notify Child Listeners of filtered samplers |
Whether listeners under the recorder (the View Results Tree) also receive the responses of filtered requests. Off by default since 4.0. |
|
The Recorder: Transactions Control window
Since 5.0, starting the recorder also opens a small always-on-top window titled Recorder: Transactions Control. It repeats the fields that change during a session (Transaction name, Naming scheme with its format, Counter start value with Set counter, Create new transaction after request (ms)) and has a Stop button, so the person recording can rename the next transaction from the browser’s side of the screen without going back to the JMeter window. Typing a new transaction name before each step of the journey is the most effective way to get a readable recording.
Grouping and transaction naming
Grouping modes
| Value | Grouping | Result |
|---|---|---|
0 |
Do not group samplers |
All samplers stored one after the other. |
1 |
Add separators between groups |
A Simple Controller named with dashes between groups, as a visual separator only. |
2 |
Put each group in a new controller |
One Simple Controller per group. |
3 |
Store 1st sampler of each group only |
Only the first request of each group is recorded, with Follow Redirects and Retrieve All Embedded Resources turned on, so the replay fetches the page’s resources itself. |
4 |
Put each group in a new transaction controller |
One Transaction Controller per group (the Recording template’s choice). |
A new group starts when the gap since the previous request exceeds the inactivity time (Create new transaction after
request (ms), or proxy.pause, 5000 ms by default) or, in 5.6.3, when the transaction name is changed. The new
controller is named after the transaction name (the first sampler’s name when the field is empty). Transaction
Controllers created by the recorder have Include duration of timer and pre-post processors in generated sample
explicitly set to false, so the recorded plan already states it; keep it that way
(Transaction Controller).
Worked example. With the default 5000 ms gap and no transaction name typed, the person recording submits the
login form: the front end sends POST /api/auth/login at t = 0 s and then loads the start page’s book list with
GET /api/books?query= at t = 0.2 s. The person types a search term and submits it at t = 6.4 s
(GET /api/books?query=tolkien), then clicks a book at t = 9.1 s (GET /api/books/42). The gaps are 0.2 s,
6.2 s and 2.7 s: only the second exceeds 5 s, so two groups are created, the login with the start-page call, and the
search together with the book view, because the person clicked the book less than 5 s after searching. Either leave
more than 5 s between clicks or type a new transaction name before each click; the second is reliable and gives better
names.
Naming schemes
The recorder numbers samplers with a running counter as long as proxy.number.requests is true (the default). Since
5.2 the number is appended at the end instead of the beginning. With Transaction name = 02 Search and the request
GET /api/books?query=tolkien as the fifth recorded request, the four schemes give:
| Naming scheme | Rule in 5.6.3 | Sampler name |
|---|---|---|
Prefix |
Transaction name, then the path, then |
|
Transaction name |
Transaction name, then |
|
Suffix |
Transaction name, |
|
Use format string |
The Naming format (default |
|
The component reference states that the default is Transaction name. The Recording template sets it explicitly.
With Use format string the placeholders are #{name} (transaction name), #{path}, #{method}
(5.6), #{host} (the domain in the sampler GUI), #{scheme} (the protocol), #{port}, #{url}
and #{counter}; Java MessageFormat options are accepted inside a placeholder. For an API, a format that keeps
the method readable is the most useful:
# user.properties: default format for "Use format string"
proxy.sampler_format=#{counter,number,000} #{method} #{path} (#{name})
which names the sampler above 005 GET /api/books (02 Search). The URL query string is not part of #{path}.
Include and exclude patterns
The URL patterns are regular expressions (Jakarta ORO by default) matched against a string made of the host, the port
(explicit or implied), the path and the query. For http://192.168.1.20:8080/api/books?query=tolkien that string is:
192.168.1.20:8080/api/books?query=tolkien
The pattern must match the whole string, not a part of it: \.html does not match localhost:80/index.html, but
.*\.html(\?.*)? does. The rules are:
-
If there are include patterns, a URL must match at least one of them to be recorded.
-
If there are exclude patterns, a URL must match none of them to be recorded.
-
With neither, everything is recorded, which typically means dozens of samplers per page for images, stylesheets, scripts and fonts.
The manual’s advice is to filter out everything not of interest: the replay can fetch embedded resources itself (Retrieve All Embedded Resources on the HTTP Request), so recording them only clutters the plan. For the Bookshelf journey only the API matters, so an include pattern is the cleanest filter, with excludes as a safety net:
URL Patterns to Include
.*:8080/api/.*
URL Patterns to Exclude
(?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff|woff2)([\?;].*)?
.*/favicon\.ico
Add suggested Excludes inserts the patterns of the property proxy.excludes.suggested (several patterns separated by
;). Its default in the properties reference is .*\\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff|woff2) (the double
backslash is the properties-file escape); the 5.6.3 source adds (?i) in front so the match is case-insensitive.
Two further notes from the manual: matching is case-sensitive unless the pattern says otherwise, and after changing patterns while recording, click Restart so they take effect.
Source: Includes and Excludes (Component Reference) and Using the HTTP(S) Test Script Recorder (Best Practices)
The content-type filter
The content-type filter works on the response, and its patterns only need to be contained in the content type.
The properties proxy.content_type_include and proxy.content_type_exclude provide default values for a new
recorder. The properties reference gives their defaults as text/html|text/plain|text/xml and
image/.*|text/css|application/.*; in the shipped jmeter.properties those lines are commented out, and in 5.6.3
the GUI copies them into the fields only when they are defined, so a new recorder starts with both fields empty.
That matters for an API: if the documented example values are enabled, application/json responses match the
application/.* exclusion and every Bookshelf API call is filtered out. For a JSON API either leave the fields
empty and filter on URLs, or include JSON explicitly:
Content-type filter Include: application/json|text/html Exclude: (empty)
Filtering flow
Headers, cookies and authorization in a recording
With Capture HTTP Headers on, each sampler gets its own HTTP Header Manager. The recorder edits it on the way:
-
Cookie is always removed. The browser handles cookies while recording and the recorder passes them through, but does not store them because they are likely to change between runs. Add an HTTP Cookie Manager to the plan (the Recording template already has one).
-
proxy.headers.removelists the other removed headers. Its default isIf-Modified-Since,If-None-Match,Host. The conditional headers are removed because a recording should download all content rather than be answered from the browser cache with 304 responses.Hostis rebuilt by the sampler from the server name. Add headers to the list rather than deleting them by hand after every recording:# user.properties: also drop headers the replay must not send proxy.headers.remove=If-Modified-Since,If-None-Match,Host,Sec-Fetch-Site,Sec-Fetch-Mode,Sec-Fetch-Dest -
Authorization is turned into an Authorization Manager when it can be. The component reference says Cookie and Authorization are always removed from the generated Header Managers. The 5.6.3 source is more precise: for a
Basicheader the recorder decodes the user and password into an HTTP Authorization Manager in the target controller (reusing one that was added manually) and removes the header; forDigestand other schemes it creates an entry with${AUTH_LOGIN}and${AUTH_PASSWORD}placeholders; for aBearertoken the header is left in the sampler’s Header Manager, with the recorded token value, to be correlated by hand. The manual adds that automatically computed authorization values may need fixing after recording.
For Bookshelf, which uses a JWT, the consequence is concrete: every recorded call after login carries
Authorization: Bearer eyJ... with the token of the recording session. It works for a few minutes in the GUI and fails
with 401 as soon as that token expires. Correlation (below) replaces it.
HTTP Request Defaults and variable replacement
Two mechanisms remove hard-coded values while recording, and both look at elements directly within the target controller or directly within any of its parents (so the Test Plan level counts):
-
HTTP Request Defaults. If enabled Defaults are found there, the recorded samplers have empty fields for every value the Defaults specify. An HTTP Request Defaults placed directly inside the recorder overrides them for this purpose with its non-blank values. Forgetting the Defaults means deleting the server name and port from every sampler by hand, as the tutorial warns.
-
User Defined Variables. If User Defined Variables are found there, every occurrence of a variable’s value in the recorded samplers is replaced by the variable reference: with
server = xxx.example.com, the stringxxx.example.combecomes${server}wherever it occurs. UDVs placed directly in the recorder override the values to replace. Matching is case-sensitive.
With Regex Matching off, replacement is a plain substring match, so a short value replaces too much (a variable
WEB = www turns every www into ${WEB}). With Regex Matching on (the template’s choice), each value is a Perl-5
regular expression wrapped in \b( and )\b, so it only matches whole words. Rules for regex values:
-
Wrap the value in parentheses to avoid the added word boundaries, for example
('.*?'). -
Anchor a whole-string match with parentheses too:
(^thus$); the parentheses are needed because the boundaries would stop^and$from matching. -
Look-ahead works (
(^/images(?=/))); look-behind is not supported by Jakarta ORO. -
Variables are checked in random order, so values must not overlap:
.*andwwwoverlap;a+andb+do not. An overlapping regex can produce broken references such as${{regex}. -
Problems interpreting a value as a pattern are reported in
jmeter.log.
The Recording template uses this to variabilise the host and scheme. A Bookshelf recording made with
hostToRecord = 192.168.1.20 therefore produces samplers like this one, with the host gone (inherited from the
Defaults) and only the port left hard-coded because the template’s Defaults leave the port empty:
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="02 Search-5">
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="query" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.name">query</stringProp>
<stringProp name="Argument.value">tolkien</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
</elementProp>
</collectionProp>
</elementProp>
</HTTPSamplerProxy>
Check the recorded bodies and Header Managers for the old address as well (an Origin or Referer header, for
example) and make sure they use ${host} or are deleted during cleanup.
Redirects, file uploads and binary bodies
Redirects. While recording, the browser follows a redirect and sends a second request; the recorder records both.
The generated samplers have Follow Redirects selected, so at replay the first sampler would follow the redirect and
the second would repeat it. To avoid that duplicate, the recorder remembers the redirect target of a redirect response
and, if the next request matches it, records that sample disabled and adds comments to the redirect chain. This
assumes the requests of a redirect chain follow each other with nothing in between. Turn it off with
proxy.redirect.disabling=false. Planned for 6.0: 307 and 308 redirects will preserve the original method when
followed, as the HTTP specification requires; replays of recorded 307/308 flows may change behaviour.
File uploads. Some browsers (the manual names Firefox and Opera) do not send the full path of an uploaded file, which can make the recorder fail. Put the files to upload in the JMeter working directory, or start JMeter from the directory containing them.
Binary bodies. Requests whose content type is listed in proxy.binary.types are saved to files instead of being
inlined in the sampler. The properties reference default is
application/x-amf,application/x-java-serialized-object,binary/octet-stream; the files go to proxy.binary.directory
(default: the user.dir system property, the directory JMeter was started from) with the suffix
proxy.binary.filesuffix (default .binary).
Associating requests with responses. With proxy.number.requests=true (the default) samplers and the responses in
the recorder’s View Results Tree are numbered, so a sampler can be matched with its response. There may be more
responses than samplers when filters are used; filtered responses are shown with the label in brackets, for example
[23 /favicon.ico].
Protocols on top of HTTP. Binary protocols carried over HTTP (the manual’s examples are custom binary protocols,
Adobe Flex and Microsoft Silverlight) are not recorded natively; a custom SamplerCreator can translate them into
HTTP samplers. That is a JMeter extension point, covered in the extending JMeter page later in this guide.
Recording think time
A timer placed directly inside the recorder is copied into every recorded sampler, or into the first sampler of each
group when grouping is used. In the copy, every occurrence of ${T} in the timer’s properties is replaced by the gap
since the previous recorded request, in milliseconds. The Recording with Think Time template (File → Templates… →
Recording with Think Time) is the Recording template with a Uniform Random Timer inside the recorder, configured
with Constant Delay Offset ${T} and Random Delay Maximum 100:
<UniformRandomTimer guiclass="UniformRandomTimerGui" testclass="UniformRandomTimer" testname="Uniform Random Timer">
<stringProp name="ConstantTimer.delay">${T}</stringProp>
<stringProp name="RandomTimer.range">100.0</stringProp>
</UniformRandomTimer>
In the worked grouping example above, the search sampler gets a timer of 6200 ms plus up to 100 ms of jitter, because the person paused 6.2 s before searching. Recorded pauses are real but noisy: a pause during which the person answered a message becomes think time for every virtual user. Review them, cap outliers, and consider replacing them with a think-time model whose mean and spread are chosen on purpose (Recording with Think Time template).
Recording the Bookshelf journey, step by step
-
File → Templates… → Recording, with
hostToRecord = 192.168.1.20(the recording machine’s address),schemeToRecord = http,recordingOutputFile = bookshelf-recording.xml. -
In the HTTP(S) Test Script Recorder, Requests Filtering tab: add the include pattern
.*:8080/api/.*, keep the template’s excludes, leave both content-type fields empty. -
Test Plan Creation tab: keep Put each group in a new transaction controller and the Transaction name scheme; keep Capture HTTP Headers and Regex Matching on.
-
Click Start. Open a private window of a browser whose proxy is
localhost:8888and type01 Loginas the transaction name in the Recorder: Transactions Control window. -
Browse to
http://192.168.1.20:8080/, log in with a test account fromusers.csv. -
Type
02 Search, search fortolkien. Type03 View book, open a book. Type04 Create loan, borrow it. Type05 View loan, open the loan. -
Click Stop in the recorder (or in the small window), reset the browser’s proxy settings, save the plan as
bookshelf-recorded.jmx.
The Recording Controller now holds five Transaction Controllers:
Thread Group
└── Recording Controller
├── 01 Login POST /api/auth/login 01 Login-1
├── 02 Search GET /api/books?query=tolkien 02 Search-2
├── 03 View book GET /api/books/42 03 View book-3
├── 04 Create loan POST /api/loans 04 Create loan-4
└── 05 View loan GET /api/loans/7 05 View loan-5
(each sampler has its own HTTP Header Manager)
Validate it before changing anything: right-click the Thread Group, Validate, and check the samples in the View Results Tree. Then clean it up.
Cleaning up a recording
A raw recording replays one person’s session, with that person’s token, IDs and timing, against the address it was recorded from. The tutorial lists the two jobs that remain, variabilising inputs and correlating dynamic data; in practice the full list is longer and its order matters.
1. Remove the noise
-
Delete samplers that are not part of the workload under test: telemetry and analytics calls, health checks,
favicon.ico, requests to third-party hosts that the load test must not hit, and redirect samplers that the recorder already disabled. -
Decide about CORS preflight
OPTIONSrequests. A browser sends them for cross-origin calls; JMeter is not a browser and does not need them to send the real request. Keep them only if the server cost of preflights is part of what is being measured. -
Disable or remove the HTTP(S) Test Script Recorder and its View Results Tree from the plan that will be used for load, or keep them disabled; they never run in CLI mode, but they make the plan heavier to read.
-
Rename the Recording Controller (or replace it by the Transaction Controllers it contains); its name appears nowhere in the results, but it documents the plan.
2. Variabilise hosts, ports and protocol
Replace the recording-time values with run properties, using the section’s convention (host, port):
-
In the HTTP Request Defaults: Protocol
http, Server Name or IP${__P(host,localhost)}, Port Number${__P(port,8080)}. -
Clear the hard-coded port from every sampler (the recorder left
8080because the template’s Defaults had no port). The search/replace dialog of the GUI helps here. -
Delete the template’s User Defined Variables
hostandscheme, or define them from properties (host = ${__P(host,localhost)}) so the${host}references the recorder wrote into headers keep working.
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults">
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.domain">${__P(host,localhost)}</stringProp>
<stringProp name="HTTPSampler.port">${__P(port,8080)}</stringProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</ConfigTestElement>
3. Consolidate the headers
Each sampler has its own Header Manager with the browser’s headers: User-Agent, Accept, Accept-Language,
Accept-Encoding, Origin, Referer, the Sec-Fetch-* family and, for Bookshelf, the recorded
Authorization: Bearer header. Keep what the server actually uses:
-
Move the common headers into one HTTP Header Manager at Thread Group level:
Accept: application/json,Accept-Encoding: gzip, deflate(to keep the transfer sizes realistic) and aUser-Agentif the server cares. -
Keep
Content-Type: application/jsonon the samplers with a JSON body (login and create loan), in their own Header Manager. -
Delete the per-sampler managers and the browser-only headers. Verify the headers actually sent in the View Results Tree Request tab, because several Header Managers in scope combine in ways that are easy to get wrong (Test Plan Elements, Execution Order & Scoping).
-
Replace the recorded
Authorization: Bearer eyJ...withAuthorization: Bearer ${token}in a single Header Manager placed where it covers only the calls after login. Header handling in depth is on HTTP Sessions, Headers, Cookies & Authentication.
4. Parameterise the inputs
The recorded user name, password and search term are the recording session’s. Feed them from users.csv and
search-terms.csv with CSV Data Set Config elements and reference them as ${username}, ${password} and
${term} (Parameterization & Test Data).
5. Correlate the dynamic values
Values the server generates must be extracted from responses and reused: the JWT returned by POST /api/auth/login,
the loan ID returned by POST /api/loans and used in GET /api/loans/${loanId}, CSRF tokens and similar. The
quickest way to find them is the Search function of the View Results Tree with a recorded value (the token’s first
characters, 7 in the loan path), or recording the journey twice and comparing the two plans. The extraction itself
(JSON Extractor and friends) is on Correlation & Extractors.
6. Add what the recorder cannot know
-
Assertions on status and content, so errors are counted as errors (Assertions).
-
Think time, recorded or modelled.
-
The load model: replace the template’s 1 thread, 1 loop with the run properties,
${__P(threads,1)},${__P(rampup,1)}and a scheduler duration of${__P(duration,60)}; for the section’s load profile run it with-Jthreads=50 -Jrampup=60 -Jduration=660(Thread Groups: Concurrent Users & Ramp-up). -
A Transaction Controller setting stated explicitly: the recorder already sets Include duration of timer and pre-post processors in generated sample to false; keep it false in every controller added by hand.
Run the cleaned plan with one thread in the GUI, then in CLI mode, before scaling up:
jmeter -n -t bookshelf-recorded.jmx -Jthreads=1 -Jrampup=1 -Jduration=60 -l smoke.jtl
Output (illustrative, abridged):
Creating summariser <summary> Created the tree successfully using bookshelf-recorded.jmx Starting standalone test @ 2026 Oct 14 10:30:00 CEST (1791966600000) summary + 58 in 00:00:29 = 2.0/s Avg: 37 Min: 6 Max: 212 Err: 0 (0.00%) Active: 1 Started: 1 Finished: 0 summary + 62 in 00:00:30 = 2.1/s Avg: 35 Min: 5 Max: 198 Err: 0 (0.00%) Active: 0 Started: 1 Finished: 1 summary = 120 in 00:01:00 = 2.0/s Avg: 36 Min: 5 Max: 212 Err: 0 (0.00%) Tidying up ... @ 2026 Oct 14 10:31:00 CEST (1791966660000) ... end of run
Zero errors with one thread only proves the correlation works; it says nothing about performance yet. A plan that replays with 401 errors after a few minutes still carries a recorded token.
Recording traffic of non-browser clients
The recorder is an ordinary HTTP proxy, so any client that can be pointed at a proxy can be recorded: a mobile app in
an emulator, a desktop or CLI client, another service calling the Bookshelf API. Two things are needed: the client
must send its requests through JMeter-host:8888, and for HTTPS it must trust the JMeter root CA.
curl has a proxy option and a CA option of its own. The CA file is the exported certificate:
# Plain HTTP: the request goes through the recorder and is recorded
curl -x http://localhost:8888 "http://192.168.1.20:8080/api/books?query=tolkien"
# HTTPS: trust the JMeter root CA for this call only
curl -x http://localhost:8888 \
--cacert "$JMETER_HOME/bin/ApacheJMeterTemporaryRootCA.crt" \
"https://bookshelf.example.com/api/books?query=tolkien"
A Java client uses the standard networking system properties for the proxy and a trust store containing the JMeter
CA, created with the JDK’s keytool:
# Put the JMeter root CA into a throw-away trust store
keytool -importcert -noprompt -alias jmeter-recording \
-file "$JMETER_HOME/bin/ApacheJMeterTemporaryRootCA.crt" \
-keystore recording-truststore.jks -storepass changeit
# Run the client through the recorder
java -Dhttp.proxyHost=192.168.1.20 -Dhttp.proxyPort=8888 \
-Dhttps.proxyHost=192.168.1.20 -Dhttps.proxyPort=8888 \
-Djavax.net.ssl.trustStore=recording-truststore.jks -Djavax.net.ssl.trustStorePassword=changeit \
-jar bookshelf-cli.jar search tolkien
The proxy host is the machine’s address rather than localhost because the JDK excludes loopback addresses from
proxying by default (http.nonProxyHosts). changeit is a placeholder for a throw-away trust store, not a secret to
reuse; delete the trust store when the recording is done.
Points that apply to every non-browser client:
-
Certificate pinning defeats the recorder. A client that pins the server’s certificate or public key refuses the generated certificate by design; such traffic cannot be recorded without changing the client.
-
Only HTTP samplers come out. The recorder creates HTTP Request (or GraphQL HTTP Request) samplers. Core JMeter has no HTTP/2, WebSocket or gRPC sampler, so such traffic cannot become a faithful sampler through recording (the gRPC load testing page later in this guide covers the plugin route for gRPC).
-
Emulators and devices need their own proxy setting and must trust the JMeter CA in the store the app uses; the JMeter manual does not cover devices, so follow the platform’s documentation.
Tools → Import from cURL
Import from cURL (added in 5.1, extended in 5.2 with a file import and fuller option coverage, and with line
continuation support in 5.4) creates HTTP Request samplers from curl command lines. It does not run curl and does
not contact the server; it only parses the text.
The dialog
Open it with Tools → Import from cURL. Its fields:
| Field | Meaning |
|---|---|
Text area |
One or more |
Read cURL commands from file |
A text file with cURL commands, read as UTF-8, one command per line. Use either the file or the text area, not both; filling in both is reported as an error. |
Add cookie header to Cookie Manager |
When ticked, cookies sent with |
Create Test Plan |
Parses the commands and creates the elements; the status line shows "Successfully created HTTP Request(s)" or the error. |
Where the elements go
The 5.6.3 behaviour, from the source:
-
No Thread Group in the plan. The current plan is cleared and replaced by a new one: a Test Plan whose comment explains how to run it, a Thread Group, the HTTP Request with its Header Manager, a Cookie Manager, and a View Results Tree. The Thread Group is already parameterised with the same property names as this section:
Thread Group field Value Meaning Number of Threads
${__P(threads,10)}10 unless
-Jthreadsis given.Ramp-up period
${__P(rampup,30)}30 s unless
-Jrampupis given.Loop Count
${__P(iterations,-1)}Infinite unless
-Jiterationsis given.Specify Thread lifetime
on
Scheduler enabled.
Duration
${__P(duration,3600)}3600 s unless
-Jdurationis given.Startup delay
5
5 s before the first thread starts.
With the defaults that is 10 threads started over 30 s, one every 3 s, after a 5 s startup delay, with a scheduled duration of 3600 s. Run with
-Jthreads=50 -Jrampup=60 -Jduration=660it becomes the section’s load profile: 50 threads, one new thread every 1.2 s, all running after 60 s, then a 10-minute hold. -
A Thread Group exists and a controller is selected. The sampler is added under the selected controller (Module and Include Controllers excepted).
-
A Thread Group exists and something else is selected. The sampler is added under the first Thread Group.
Each sampler is named HTTP Request and gets the comment Created from cURL on <date and time>, followed by warnings
about options that were ignored or need configuring elsewhere. Rename the samplers immediately: dozens of samplers
named HTTP Request make the results unreadable.
Supported options
The cURL page lists the options below. The Result in JMeter column comes from the 5.6.3 parser, since the manual
only describes what each option means to curl.
| curl option | Result in JMeter |
|---|---|
URL (positional or |
Protocol, server, port, path and query of the sampler. Follow Redirects is always set. |
|
HTTP method. |
|
A row in the sampler’s Header Manager. |
|
Adds |
|
|
|
|
|
Cookies in an HTTP Cookie Manager; a value that is not |
|
A raw request body (Body Data). The method becomes POST if it was GET. |
|
Multipart form (Use multipart/form-data on); |
|
Moves the data to the query string and uses GET. |
|
HEAD request. |
|
An HTTP Authorization Manager entry with that user, password and mechanism. |
|
Connect timeout, and a response timeout equal to max-time minus connect-timeout, in milliseconds. |
|
Use KeepAlive off. |
|
Proxy server, port (1080 if not given), user and password in the sampler’s Advanced tab. |
|
Source address of the sampler, as an interface name or address. |
|
A DNS Cache Manager using those servers (the manual describes it as resolving over DNS over HTTPS). |
|
A DNS Cache Manager static host entry. |
|
Not applied. The sampler comment asks to configure the SSL file in |
|
Accepted, no effect (JMeter does not verify server certificates by default). |
|
Accepted; Follow Redirects is set on every imported sampler regardless. |
|
Not applied per sampler; the comment points to the property |
|
Not applied per sampler; the comment points to |
|
Not applied; the comment explains that the non-proxy host list is set on the command line when starting JMeter. |
|
Ignored, and listed as ignored in the comment. |
|
Not supported, and listed as such in the comment. |
When an option is ignored or needs configuration elsewhere, the warning is written into the HTTP Request’s comment field, as the cURL page describes.
Source: Curl options supported (Curl, User’s Manual), Warning (Curl, User’s Manual) and BasicCurlParser.java in JMeter 5.6.3
The manual’s own examples show the common forms:
curl -X POST "https://example.invalid" -b 'username=Tom;password=123456'
curl -X POST "https://example.invalid" --data 'fname=a&lname=b'
curl -X POST "https://example.invalid" -F 'lname=a' -F 'fname=b' -F 'c=@C:\Test\test.txt'
curl 'https://example.invalid/' -x 'https://aa:bb@proxy.invalid:8042'
curl "https://example.invalid" -u 'user:passwd' --basic
curl "https://example.invalid" --dns-servers '0.0.0.0,1.1.1.1'
Source: Examples (Curl, User’s Manual)
Bookshelf example: from DevTools to a sampler
In Chrome or Edge DevTools, Network panel, right-click the request and choose Copy → Copy as cURL (bash) (Firefox:
Copy Value → Copy as cURL). Copying the login call of the Bookshelf front end gives a command like this (browser
headers shortened, test credentials from users.csv):
curl 'http://localhost:8080/api/auth/login' \
-H 'Accept: application/json' \
-H 'Accept-Language: en-GB,en;q=0.9' \
-H 'Content-Type: application/json' \
-H 'Cookie: theme=dark; JSESSIONID=4F2C0E1B' \
-H 'Origin: http://localhost:8080' \
-H 'Referer: http://localhost:8080/login' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64)' \
--data-raw '{"username":"reader01","password":"test-password-01"}'
Steps:
-
Open an empty plan (File → New), so the import creates the full parameterised plan.
-
Tools → Import from cURL, paste the command, leave Add cookie header to Cookie Manager unticked, click Create Test Plan.
-
Rename the sampler to
POST /api/auth/login; in the Header Manager deleteAccept-Language,Origin,RefererandUser-Agent; move the host to the HTTP Request Defaults as in the cleanup above. -
Replace the body with
{"username":"${username}","password":"${password}"}fed fromusers.csv.
The imported sampler, after the rename and the header cleanup, is:
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /api/auth/login">
<stringProp name="TestPlan.comments">Created from cURL on 2026-10-14T10:45:12.3</stringProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.path">/api/auth/login</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{"username":"${username}","password":"${password}"}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
</HTTPSamplerProxy>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP HeaderManager">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Accept</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
What happened to each part of the command: the Cookie header was dropped (checkbox unticked; had it been ticked,
theme=dark would have gone to the Cookie Manager and JSESSIONID would still have been skipped); --data-raw made
the method POST and became the raw body; nothing went into the comment because no option was ignored. A second
command, curl 'http://localhost:8080/api/loans' -H 'Authorization: Bearer eyJ...' --data-raw '{"bookId":42}',
imported into the same plan with the Thread Group selected, lands under the Thread Group; its recorded token must be
replaced with ${token} exactly as for a recording.
Pitfalls specific to the import:
-
It clears an unsaved plan that has no Thread Group. Save first, or add a Thread Group, if the open plan matters.
-
The text area splits on the word
curl. Pasted text is cut at every occurrence ofcurland each piece is parsed as a command, so a URL, header or body containing that word breaks the import. Use the file import (one command per line) or edit the word out and restore it afterwards. -
A file holds one command per line. Multi-line commands with
\continuations work in the text area, not in the file. -
Windows
cmdcopies are different. Copy as cURL (cmd) uses^escapes that are meant forcmd.exe; copy the bash form. -
The import is a snapshot of one session. Tokens, cookies, CSRF values and IDs in the command are the copied session’s; correlate them like a recording.
Load-generator cost
Neither tool runs during a load test, so their cost is at authoring time, and the cost that matters is the one they build into the plan:
-
Recording runs in the GUI. The recorder handles requests one at a time for one person; certificate generation on the first Start takes a noticeable moment and freezes the GUI. The recorder’s View Results Tree keeps every response in memory and writes it to
recordingOutputFile, so a long recording session grows the JMeter heap; clear it between sessions. -
Embedded resources multiply the requests. Retrieve All Embedded Resources, set by grouping mode 3 or by hand, makes each sampler parse the response and fetch its resources (in parallel when the concurrent pool is used). That can turn one sampler into twenty requests and is CPU-heavy on the load generator. For an API test keep it off.
-
One Header Manager per sampler costs memory per thread. Every thread clones the test elements it uses, so 50 samplers with 15 headers each are 750 header entries per thread. Consolidating them is cheaper as well as cleaner.
-
Recorded think time sets the thread count. Long recorded pauses mean more threads for the same throughput (see the Little’s-Law maths on Timers & Think Time).
-
The Uniform Random Timers from the think-time template cost nothing measurable; their delays do.
Pitfalls
-
Treating a recording as a test. Without cleanup and correlation it replays one session’s token and IDs and fails, or worse, succeeds against cached or shared data.
-
Nothing is recorded. The browser bypasses the proxy (loopback address, bypass proxy for local addresses, a PAC file), there is no Recording Controller under a Thread Group, or a content-type exclusion such as
application/.*removes every JSON call. -
Too much is recorded. No include pattern, so static files, analytics and browser background calls (update checks, safe-browsing lookups; the template excludes many of them) end up in the plan.
-
Forgetting the HTTP Request Defaults before recording, then editing every sampler by hand.
-
Leaving the JMeter CA trusted in a browser or operating system after recording.
-
Using an expired certificate: the 7-day validity catches out every recording session that starts a week after the last one.
-
Recording through a corporate proxy without
-H/-P, then replaying without them. -
Grouping by accident. Clicking faster than
proxy.pausemerges two pages into one transaction; pausing longer in the middle of a page splits it. Name transactions explicitly. -
Keeping recorded
Authorization: Bearerheaders. They are not removed for Bearer tokens, whatever the manual’s summary says. -
Expecting JavaScript to run at replay. JMeter is not a browser: it replays the requests, not the page logic. Front-end performance belongs to browser-based tools (Playwright, Cypress and Selenium are the subject of issue #258).
From the books to 5.6 to 6.0
| Version | What changed | Impact on this page |
|---|---|---|
2.x (Cookbook, PTwJ) |
Recorder called HTTP Proxy Server, added to the WorkBench. 2.10 introduced per-host certificates generated with
|
The WorkBench steps in the books no longer apply. The Cookbook’s browser-extension and HAR-converter recipes use third-party services and are not covered. |
3.0 (Pro) |
Default recorder port 8888, as in the Recording template. |
Pro ch. 4 still records into the WorkBench. |
3.2, 3.3 |
Recorder GUI simplified; clearer certificate expiration information and Start/Stop behaviour; |
The fields table above. |
4.0 |
WorkBench removed: non-test elements, including the recorder, are direct children of the Test Plan (old plans are migrated on load). Naming by prefix or by transaction name; Notify Child Listeners of filtered samplers off by default; SAN added to the generated CA. |
Use the Recording template. |
5.0 |
The Recorder: Transactions Control window to rename transactions while browsing. |
Name each step of the journey. |
5.1 |
Tools → Import from cURL. |
Import section above. |
5.2 |
Recorder appends the request number at the end of names; cURL import from a file and full option coverage. |
Naming table above. |
5.3, 5.4 |
Transaction or simple controllers named after the prefix without a number suffix (5.3); Use format string naming
(5.4); cURL commands with |
Naming and import sections above. |
5.6 |
More format placeholders ( |
Format example above. |
6.0 (planned) |
Java 17 required; malformed percent-encoded URLs logged as a warning while recording instead of failing; 307/308
redirects keep the method; expired cookies no longer sent by the Cookie Manager; no |
Re-validate recorded plans that rely on 307/308 redirects, long-lived cookies or multipart uploads. |
Source: Changes history, Changes and changes.xml on master
References
-
HTTP(S) Test Script Recorder (Component Reference), including Installing the JMeter CA certificate for HTTPS recording
-
Test Script Recorder configuration and Test Script Recorder certificate configuration (Properties Reference)
-
Apache JMeter source: 5.6.3
bin/templates/recording.jmx,recording-with-think-time.jmx,bin/jmeter.properties, the recorder classes undersrc/protocol/http/.../proxy, the cURL import classes, andxdocs/changes.xmlon master for planned 6.0 changes -
Sai Matam and Jagdeep Jain, Pro Apache JMeter, Apress, 2017 (JMeter 3.0), ch. 4: recording through the proxy and the WorkBench, used for concepts only.
-
Bayo Erinle, JMeter Cookbook, Packt, 2014 (JMeter 2.11), ch. 1: recording scripts; its third-party conversion recipes are not covered.