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 curl commands, one sampler per command.

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.

sequenceDiagram autonumber participant B as Browser (proxy set to JMeter host, port 8888) participant R as HTTP(S) Test Script Recorder participant S as Bookshelf server participant T as Target controller in the test plan Note over R: On Start - keystore proxyserver.jks created if missing, root CA exported as ApacheJMeterTemporaryRootCA.crt B->>R: CONNECT bookshelf host on port 443 R->>R: Generate a certificate for that host, signed by the JMeter root CA R-->>B: TLS handshake with the generated certificate Note over B: Accepted only if the JMeter root CA is trusted, otherwise unknown_ca B->>R: POST /api/auth/login (decrypted inside the proxy) R->>S: Same request on a new TLS connection (HTTP sampler) S-->>R: 200 OK with a JSON token R-->>B: Response passed back unchanged R->>R: Apply URL include and exclude patterns, then the content-type filter R->>R: Strip Cookie and the headers in proxy.headers.remove, apply naming and grouping R->>T: Add HTTP Request sampler plus Header Manager, in a new Transaction Controller after a pause

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.

  1. Start JMeter in GUI mode (bin/jmeter on Linux or macOS, bin\jmeterw.bat on Windows) on a machine with a JDK.

  2. Choose File → Templates… (or the Templates toolbar icon) and create a plan from the Recording template.

  3. The template has three parameters. Fill them in and confirm with Validate:

    Parameter Default Used for

    recordingOutputFile

    recording.xml

    File name of the View Results Tree placed inside the recorder, which saves every recorded request and response (headers and bodies included) to that file.

    hostToRecord

    www.example.com

    Value of the User Defined Variable host and of Server Name or IP in the HTTP Request Defaults.

    schemeToRecord

    https

    Value of the User Defined Variable scheme and of Protocol in the HTTP Request Defaults.

  4. 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.

  5. 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 -H and -P. If the browser normally reaches the network through a proxy, start JMeter with -H and -P so 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 localhost or 127.0.0.1 through a proxy, so recording http://localhost:8080 silently 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 JMeter bin directory, with a root CA key pair. Unless a keystore and alias are supplied, it does this with the JDK’s keytool, so a JRE without keytool cannot record HTTPS. If keytool is not found in the standard places, set the system property keytool.directory in bin/system.properties.

  • It exports the root CA certificate as ApacheJMeterTemporaryRootCA.crt (and ApacheJMeterTemporaryRootCA.usr for Opera) into the bin directory, 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 follow proxy.cert.directory, which defaults to bin.

  • 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.validity days, 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 ApacheJMeterTemporaryRootCA.crt, click View to compare the details, then tick Trust this CA to identify websites and confirm.

Chrome, Edge (Windows)

These use the operating system store. Open ApacheJMeterTemporaryRootCA.crt, check the Details tab against the JMeter dialog, go back to General, click Install Certificate… and follow the wizard.

Opera

Tools / Preferences / Advanced / Security → Manage Certificates… → Intermediate → Import…, then select ApacheJMeterTemporaryRootCA.usr.

Other browsers and operating systems

Import the .crt file as a trusted root authority in the store the browser uses (the browser’s own store, or the operating system’s). The JMeter manual documents only the browsers above.

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

proxy.cert.directory

JMeter bin directory

Directory of the keystore.

proxy.cert.file

proxyserver.jks

Keystore file name. Deleting this file forces regeneration of the keystore and of the exported certificates.

proxy.cert.type

JKS

Keystore type. Ignored when JMeter generates its own certificate.

proxy.cert.keystorepass

password

Keystore password. Ignored when JMeter generates its own certificate (it then uses a random password).

proxy.cert.keypassword

password

Key password. Ignored when JMeter generates its own certificate.

proxy.cert.factory

SunX509

Key manager factory algorithm. Ignored when JMeter generates its own certificate.

proxy.cert.alias

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).

proxy.cert.validity

7

Validity in days of the certificates JMeter generates.

proxy.cert.dynamic_keys

true

Generate one certificate per host. If false, JMeter falls back to a single key, which may not work for embedded resources on other hosts.

proxy.ssl.protocol

TLS

Protocol of the SSLContext the recorder uses towards the browser. The properties reference and the 5.6.3 source say TLS; the component reference still says SSLv3, which is out of date.

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; unknown_ca in jmeter.log

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 ApacheJMeterTemporaryRootCA.crt as a trusted authority in the right store and restart the browser.

It worked last week, now every HTTPS page fails

The 7-day validity expired, or proxyserver.jks was regenerated and the browser still trusts the old CA.

Install the new .crt (and remove the old one), or regenerate as shown above.

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 (proxy.cert.alias set or proxy.cert.dynamic_keys=false).

Use dynamic mode with the root CA installed, or browse to each secure host once to accept it; jmeter.log lists the secure domains involved.

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 keytool cannot be found

JMeter runs on a JRE without keytool, or keytool is in a non-standard place.

Run JMeter on a JDK, or set keytool.directory in system.properties.

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.

ProxyControlGui.port

HTTPS Domains

Host names or one-level wildcards to pre-generate certificates for (see above).

ProxyControlGui.domains

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.

ProxyControlGui.grouping_mode (0 to 4)

Capture HTTP Headers

Adds a Header Manager to each recorded sampler with the request headers, minus Cookie and the headers in proxy.headers.remove. See Headers, cookies and authorization in a recording.

ProxyControlGui.capture_http_headers

Add Assertions

Adds an empty Response Assertion to each sampler, as a placeholder to fill in.

ProxyControlGui.add_assertion

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.

ProxyControlGui.regex_match

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.

ProxyControlGui.proxy_prefix_http_sampler_name

Naming scheme

Prefix, Transaction name, Suffix or Use format string. See Naming schemes.

ProxyControlGui.proxy_http_sampler_naming_mode (0 to 3)

Naming format

Format used when the scheme is Use format string.

ProxyControlGui.proxy_http_sampler_format

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 proxy.pause (5000 ms).

ProxyControlGui.proxy_pause_http_sampler

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.

ProxyControlGui.default_encoding

Retrieve All Embedded Resources

Sets that option in the generated samplers (off by default).

ProxyControlGui.sampler_download_images

Redirect Automatically

Sets that option in the generated samplers (off by default).

ProxyControlGui.sampler_redirect_automatically

Follow Redirects

Sets that option in the generated samplers (on by default). See Redirects, file uploads and binary bodies.

ProxyControlGui.sampler_follow_redirects

Use KeepAlive

Sets that option in the generated samplers (on by default).

ProxyControlGui.use_keepalive

Type

Implementation of the generated samplers: HttpClient4, Java, or empty to use the default (jmeter.httpsampler).

ProxyControlGui.sampler_type_name

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.

ProxyControlGui.detect_graphql_request

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.

ProxyControlGui.content_type_include, ProxyControlGui.content_type_exclude

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.

ProxyControlGui.include_list

URL Patterns to Exclude

Requests matching any of these are not recorded. Buttons: Add, Delete, Add from Clipboard, Add suggested Excludes (the patterns in proxy.excludes.suggested).

ProxyControlGui.exclude_list

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.

ProxyControlGui.notify_child_sl_filtered

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 -counter

02 Search/api/books-5

Transaction name

Transaction name, then -counter (the path is dropped)

02 Search-5

Suffix

Transaction name, -counter, a space, the path

02 Search-5 /api/books

Use format string

The Naming format (default proxy.sampler_format)

005 - /api/books (02 Search)

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.

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

flowchart TD A[Request passes through the proxy] --> F[Forwarded to the server, response returned to the client] F --> I{Any URL include patterns?} I -- yes --> IM{URL matches one of them?} I -- no --> E IM -- no --> X[Not recorded] IM -- yes --> E{URL matches an exclude pattern?} E -- yes --> X E -- no --> C{Content-type filter passes for the response?} C -- no --> X C -- yes --> S[Sampler created in the target controller] X --> N{Notify Child Listeners of filtered samplers?} N -- yes --> V[Response still shown in the View Results Tree under the recorder]

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.remove lists the other removed headers. Its default is If-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. Host is 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 Basic header 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; for Digest and other schemes it creates an entry with ${AUTH_LOGIN} and ${AUTH_PASSWORD} placeholders; for a Bearer token 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 string xxx.example.com becomes ${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: .* and www overlap; a+ and b+ 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

  1. File → Templates… → Recording, with hostToRecord = 192.168.1.20 (the recording machine’s address), schemeToRecord = http, recordingOutputFile = bookshelf-recording.xml.

  2. 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.

  3. 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.

  4. Click Start. Open a private window of a browser whose proxy is localhost:8888 and type 01 Login as the transaction name in the Recorder: Transactions Control window.

  5. Browse to http://192.168.1.20:8080/, log in with a test account from users.csv.

  6. Type 02 Search, search for tolkien. Type 03 View book, open a book. Type 04 Create loan, borrow it. Type 05 View loan, open the loan.

  7. 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 OPTIONS requests. 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 8080 because the template’s Defaults had no port). The search/replace dialog of the GUI helps here.

  • Delete the template’s User Defined Variables host and scheme, 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 a User-Agent if the server cares.

  • Keep Content-Type: application/json on 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... with Authorization: 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 curl commands. Several commands can be pasted at once; a command may span lines with a trailing \.

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 -H 'Cookie: ...' are added to an HTTP Cookie Manager. When not ticked (the default), a Cookie header is dropped: it is not copied into the Header Manager. Cookies passed with -b always go to the Cookie Manager.

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 -Jthreads is given.

    Ramp-up period

    ${__P(rampup,30)}

    30 s unless -Jrampup is given.

    Loop Count

    ${__P(iterations,-1)}

    Infinite unless -Jiterations is given.

    Specify Thread lifetime

    on

    Scheduler enabled.

    Duration

    ${__P(duration,3600)}

    3600 s unless -Jduration is 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=660 it 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 --url)

Protocol, server, port, path and query of the sampler. Follow Redirects is always set.

-X, --request

HTTP method.

-H, --header

A row in the sampler’s Header Manager. Host and Connection are ignored; Cookie goes to the Cookie Manager only with the checkbox ticked; a header without a colon must end with ; to be kept (as an empty header).

--compressed

Adds Accept-Encoding: gzip, deflate unless the command already has an Accept-Encoding header.

-A, --user-agent

User-Agent header (replacing any other User-Agent).

-e, --referer

Referer header.

-b, --cookie

Cookies in an HTTP Cookie Manager; a value that is not name=value is treated as a cookie file to read. Session cookies named JSESSIONID, PHPSESSID, ASPSESSIONID and connect.sid are skipped, because they belong to the original session.

-d, --data, --data-ascii, --data-binary, --data-raw, --data-urlencode

A raw request body (Body Data). The method becomes POST if it was GET. @file reads the body from a file (line breaks removed, except with --data-binary); --data-raw takes @ literally; --data-urlencode URL-encodes the data. With several -d options curl joins the pieces with &.

-F, --form, --form-string

Multipart form (Use multipart/form-data on); @file becomes a file upload. Cannot be combined with --data.

-G, --get

Moves the data to the query string and uses GET.

-I, --head

HEAD request.

-u, --user with --basic or --digest

An HTTP Authorization Manager entry with that user, password and mechanism.

--connect-timeout, -m / --max-time

Connect timeout, and a response timeout equal to max-time minus connect-timeout, in milliseconds.

--no-keepalive

Use KeepAlive off.

-x, --proxy, -U, --proxy-user

Proxy server, port (1080 if not given), user and password in the sampler’s Advanced tab.

--interface

Source address of the sampler, as an interface name or address.

--dns-servers

A DNS Cache Manager using those servers (the manual describes it as resolving over DNS over HTTPS).

--resolve host:port:address

A DNS Cache Manager static host entry.

--cacert, --capath, --ciphers, --cert-status, --cert-type (and -E, --cert, --key, --key-type)

Not applied. The sampler comment asks to configure the SSL file in system.properties; --cert also adds a Keystore Configuration element. JMeter trusts all server certificates by default anyway.

-k, --insecure

Accepted, no effect (JMeter does not verify server certificates by default).

-L, --location

Accepted; Follow Redirects is set on every imported sampler regardless.

--max-redirs

Not applied per sampler; the comment points to the property httpsampler.max_redirects.

--limit-rate

Not applied per sampler; the comment points to httpclient.socket.http.cps in jmeter.properties.

--noproxy

Not applied; the comment explains that the non-proxy host list is set on the command line when starting JMeter.

-i, --include, --keepalive-time, --raw, -o, --create-dir, -v, -s

Ignored, and listed as ignored in the comment.

--proxy-ntlm, --proxy-negotiate

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.

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'

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:

  1. Open an empty plan (File → New), so the import creates the full parameterised plan.

  2. Tools → Import from cURL, paste the command, leave Add cookie header to Cookie Manager unticked, click Create Test Plan.

  3. Rename the sampler to POST /api/auth/login; in the Header Manager delete Accept-Language, Origin, Referer and User-Agent; move the host to the HTTP Request Defaults as in the cleanup above.

  4. Replace the body with {"username":"${username}","password":"${password}"} fed from users.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 of curl and 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 cmd copies are different. Copy as cURL (cmd) uses ^ escapes that are meant for cmd.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.pause merges two pages into one transaction; pausing longer in the middle of a page splits it. Name transactions explicitly.

  • Keeping recorded Authorization: Bearer headers. 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 keytool and the ApacheJMeterTemporaryRootCA file.

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; proxy.pause editable in the GUI (Create new transaction after request (ms)).

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 \ line continuations and PUT with data (5.4); GraphQL HTTP Request (5.4).

Naming and import sections above.

5.6

More format placeholders (#{url}, #{method}, #{scheme}, #{host}, #{port}) in 5.6; the recorder decodes arguments as UTF-8 by default from 5.6.1.

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 charset added to multipart/form-data automatically.

Re-validate recorded plans that rely on 307/308 redirects, long-lived cookies or multipart uploads.

References