HTTP Requests

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, the latest release on 2026-10-14. It runs on Java 8 or later, and Java 17 or later (21 recommended) is the baseline used throughout this section. JMeter 6.0 is not released: the changes planned for it in xdocs/changes.xml on the master branch (several of which touch the HTTP sampler, listed at the end of this page) are described as planned, not as shipped behaviour. HTTP/2 needs the community bzm-http2 plugin from the JMeter Plugins catalogue (Plugins Manager 2.0); everything else on this page is core JMeter. Field names, menu paths, properties and defaults come from the component reference, the properties reference and, where those are silent, the 5.6.3 source code, which is then named as the source. The books that cover the HTTP sampler (Sai Matam and Jagdeep Jain, Pro Apache JMeter, chapters 3 and 5, on JMeter 3.0; Bayo Erinle, JMeter Cookbook, chapter 8, on JMeter 2.11) still offer the HttpClient3.1 implementation, which no longer exists.

The HTTP Request sampler is the element most JMeter plans are built around. It sends one HTTP or HTTPS request, waits for the response and records a sample: the status, headers, body, timings and byte counts that every listener, assertion and report works from. Around it sit configuration elements that supply what the sampler leaves out: the HTTP Request Defaults (protocol, host, port, timeouts), the HTTP Header Manager, the HTTP Cookie Manager, the Keystore Configuration and the DNS Cache Manager. This page sets up an HTTP test from scratch against the Bookshelf API, explains every field of the sampler and of its defaults, covers HTTPS, client certificates, DNS and bandwidth emulation, and ends with the first run of bookshelf-load.jmx in CLI mode. Headers, cookies, sessions and authentication have their own page, HTTP Sessions, Headers, Cookies & Authentication; JSON bodies, GraphQL and SOAP are on REST, JSON, GraphQL & SOAP APIs.

JMeter works at the protocol level. It is not a browser: it sends the requests it is told to send, does not run JavaScript and does not render pages, so its timings are server and network timings, not page-load times as a user experiences them. Front-end performance is the subject of the Playwright, Cypress and Selenium section tracked in issue #258.

Where the HTTP elements sit in a test plan

A minimal HTTP test needs a Thread Group, one HTTP Request Defaults, the managers the requests need and the samplers. Defaults and managers placed directly under the Thread Group (or the Test Plan) apply to every sampler below them; placed under one sampler, they apply to that sampler only. The scoping and merging rules are explained on Test Plan Elements, Execution Order & Scoping.

Test Plan  bookshelf-load.jmx
└── Thread Group "Borrowers"            threads, ramp-up, loops from properties
    ├── HTTP Request Defaults           http, ${__P(host,localhost)}, ${__P(port,8080)}, timeouts
    ├── HTTP Header Manager             Accept: application/json
    ├── HTTP Cookie Manager             one per thread group
    ├── DNS Cache Manager               optional: per-thread DNS, static hosts
    ├── Keystore Configuration          optional: client certificates (mTLS)
    ├── GET /api/books                  HTTP Request: method and path only
    │   └── (assertions, extractors for this request)
    └── GET /api/books/{id}             HTTP Request

When a thread reaches a sampler, JMeter merges the configuration in scope into a copy of the sampler, runs the pre-processors and timers, sends the request and hands the result to post-processors, assertions and listeners. The sequence for one HTTP sample:

sequenceDiagram participant T as Thread (virtual user) participant S as HTTP Request sampler participant D as HTTP Request Defaults participant H as Header Manager participant C as Cookie Manager participant API as Bookshelf API participant R as SampleResult T->>S: next sampler in tree order D-->>S: fill empty fields: protocol, host, port, timeouts, implementation T->>T: pre-processors, then timers (think time) H-->>S: request headers (Accept, Authorization) C-->>S: Cookie header for cookies matching the URL S->>API: GET /api/books?query=java API-->>S: 200 + response headers + JSON body S->>C: store Set-Cookie values for later requests S->>R: elapsed, latency, connect time, code, bytes, headers, body R-->>T: post-processors, assertions, listeners

The timings in the result are defined in the glossary: elapsed time runs from just before the request is sent to just after the last byte of the response is received; latency from just before sending to just after the first part of the response arrives; connect time is the time to open the connection, including the TLS handshake. Connect time is not subtracted from latency, and it is only non-zero when a new connection is opened, so with keep-alive most samples of a thread show a connect time of 0.

Setting up an HTTP test from scratch

The steps below create the first version of bookshelf-load.jmx: one Thread Group, the HTTP Request Defaults pointing at the local Bookshelf API, and one GET /api/books request. The Bookshelf API runs on localhost:8080, started with Docker Compose as described in Compose for Local Development; its endpoints are the ones built on REST APIs.

  1. Start JMeter in GUI mode (bin/jmeter), which is meant for building the plan, not for running load. Choose File → New, select Test Plan and set Name to Bookshelf load.

  2. Right-click Test Plan and choose Add → Threads (Users) → Thread Group. Name it Borrowers and set Number of Threads to ${__P(threads,1)}, Ramp-up period to ${__P(rampup,1)} and Loop Count to ${__P(loops,10)}. The fields are explained on Thread Groups: Concurrent Users & Ramp-up.

  3. Right-click Borrowers and choose Add → Config Element → HTTP Request Defaults. On the Basic tab set Protocol to http, Server Name or IP to ${__P(host,localhost)} and Port Number to ${__P(port,8080)}. On the Advanced tab set Timeouts (milliseconds) to Connect 5000 and Response 30000, and leave the implementation as HttpClient4.

  4. Right-click Borrowers and choose Add → Config Element → HTTP Header Manager. Add a row Name Accept, Value application/json.

  5. Right-click Borrowers and choose Add → Config Element → HTTP Cookie Manager. The Bookshelf API is stateless (JWT), but adding the manager now costs nothing and is the normal arrangement for a user that keeps cookies.

  6. Right-click Borrowers and choose Add → Sampler → HTTP Request. Name it GET /api/books, leave Protocol, Server Name or IP and Port Number empty (they come from the defaults), set Method to GET, Path to /api/books, and on the Parameters tab add a row with Name query, Value java and URL Encode? selected.

  7. Save with File → Save as bookshelf-load.jmx.

Because host and port are properties with defaults, the same plan runs against localhost:8080 by default and against any other environment with -Jhost=... -Jport=..., without editing the file. Property names threads, rampup, duration, host and port are used for the same purposes on every page of this section.

The Defaults path is not a prefix

The HTTP Request Defaults Path field is the default for the whole path, used only by samplers whose path is empty; it is not a prefix added in front of the sampler paths. The component reference states this explicitly. To keep a common prefix such as /api in one place, define it as a variable on the Test Plan (User Defined Variables: basePath = ${__P(basePath,/api)}) and write the sampler paths as ${basePath}/books and ${basePath}/books/${id}. Variables, properties and the __P function are explained on Properties, Variables & Functions.

The resulting plan fragment

The .jmx saved by the GUI for the Thread Group’s children looks like this (the Thread Group itself is shown on the thread groups page). The property names are those of the 5.6.3 HTTPSamplerBaseSchema; an empty stringProp means "not set here, inherit".

<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
    <collectionProp name="Arguments.arguments"/>
  </elementProp>
  <stringProp name="HTTPSampler.protocol">http</stringProp>
  <stringProp name="HTTPSampler.domain">${__P(host,localhost)}</stringProp>
  <stringProp name="HTTPSampler.port">${__P(port,8080)}</stringProp>
  <stringProp name="HTTPSampler.path"></stringProp>
  <stringProp name="HTTPSampler.contentEncoding"></stringProp>
  <stringProp name="HTTPSampler.connect_timeout">5000</stringProp>
  <stringProp name="HTTPSampler.response_timeout">30000</stringProp>
  <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
</ConfigTestElement>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
  <collectionProp name="HeaderManager.headers">
    <elementProp name="" elementType="Header">
      <stringProp name="Header.name">Accept</stringProp>
      <stringProp name="Header.value">application/json</stringProp>
    </elementProp>
  </collectionProp>
</HeaderManager>
<hashTree/>
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
  <collectionProp name="CookieManager.cookies"/>
  <boolProp name="CookieManager.clearEachIteration">false</boolProp>
</CookieManager>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /api/books" enabled="true">
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
    <collectionProp name="Arguments.arguments">
      <elementProp name="query" elementType="HTTPArgument">
        <boolProp name="HTTPArgument.always_encode">true</boolProp>
        <stringProp name="Argument.name">query</stringProp>
        <stringProp name="Argument.value">java</stringProp>
        <stringProp name="Argument.metadata">=</stringProp>
        <boolProp name="HTTPArgument.use_equals">true</boolProp>
      </elementProp>
    </collectionProp>
  </elementProp>
  <stringProp name="HTTPSampler.domain"></stringProp>
  <stringProp name="HTTPSampler.port"></stringProp>
  <stringProp name="HTTPSampler.protocol"></stringProp>
  <stringProp name="HTTPSampler.contentEncoding"></stringProp>
  <stringProp name="HTTPSampler.path">/api/books</stringProp>
  <stringProp name="HTTPSampler.method">GET</stringProp>
  <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
  <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
  <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
  <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
  <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
  <stringProp name="HTTPSampler.connect_timeout"></stringProp>
  <stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>

The sampler’s test class is HTTPSamplerProxy whatever implementation is chosen: the proxy picks HttpClient4 or Java at run time from the HTTPSampler.implementation property (or the defaults, or the jmeter.httpsampler property). The official bin/templates/build-web-test-plan.jmx uses the same element and property names.

How a field gets its value

flowchart TD A[Sampler field] --> B{Set on the sampler?} B -- Yes --> V[Use the sampler value] B -- No --> C{Set on an HTTP Request Defaults in scope?} C -- Yes --> W[Use the nearest defaults value] C -- No --> X[Built-in default: HTTP, port 80 or 443, no timeout, jmeter.httpsampler]

Two exceptions follow from the GUI. Check boxes have only two states, so the component reference explains that an unselected box means "use the default" and a selected one means "on": a later element can switch an option on, but cannot switch it off. And a Path that starts with http:// or https:// is a full URL: the protocol, server and port fields, including those from the defaults, are then ignored.

Every field of the HTTP Request

The HTTP Request panel has two tabs, Basic and Advanced. The tables list every field in JMeter 5.6.3 with the English GUI label, the property stored in the .jmx file and what the component reference says about it. The defaults column gives the value of a newly added sampler.

Basic tab: web server and request

GUI field .jmx property Default Meaning

Name, Comments

testname attribute, TestPlan.comments

HTTP Request

The name is the sample label in every listener and report, so give each request a stable, readable name such as GET /api/books. Do not put changing values (IDs, variables) in it, or the report gets one row per value.

Protocol [http]

HTTPSampler.protocol

empty (http)

http, https or file. Empty means the defaults or http. The file protocol is for testing purposes only.

Server Name or IP

HTTPSampler.domain

empty

Host name or IP address, without http://. Required unless HTTP Request Defaults provide it or the path is a full URL. If a Host header is set in a Header Manager, it is used as the virtual host name.

Port Number

HTTPSampler.port

empty (80, or 443 for https)

Port of the web server. All port values are treated equally: a sampler without a port uses the defaults' port.

Method

HTTPSampler.method

GET

GET, POST, HEAD, TRACE, OPTIONS, PUT, DELETE, PATCH (PATCH not with the Java implementation). With HttpClient4 the WebDAV methods COPY, LOCK, MKCOL, MOVE, PROPFIND, PROPPATCH, UNLOCK, REPORT, MKCALENDAR and SEARCH are also available, plus the methods listed in httpsampler.user_defined_methods.

Path

HTTPSampler.path

empty

Path to the resource, for example /api/books. Query parameters go in the Parameters tab. A path starting with http:// or https:// is used as the full URL (protocol, server and port are then ignored, and so are parameters for GET and DELETE). The path is not encoded apart from spaces becoming %20, so unsafe characters must be encoded by hand to avoid a URISyntaxException.

Content encoding

HTTPSampler.contentEncoding

empty (UTF-8 since 5.6.1)

The character encoding used for the request (for POST, PUT, PATCH and FILE). It is not the Content-Encoding header (gzip and so on), which is set in a Header Manager.

Redirect Automatically

HTTPSampler.auto_redirects

cleared

Lets the HTTP library follow redirects itself, so they are invisible to JMeter and do not appear as samples. Only for GET and HEAD; the HttpClient sampler rejects it for POST and PUT. Cookies and headers are only sent with the first request, which breaks cross-host redirects.

Follow Redirects

HTTPSampler.follow_redirects

selected

Only effective when Redirect Automatically is off. JMeter follows the redirect itself and records each hop as a sub-sample; the parent sample takes its URL and data from the final response, its elapsed time and bytes from all hops and its latency from the first response.

Use KeepAlive

HTTPSampler.use_keepalive

selected

Sends Connection: keep-alive and reuses connections. It works properly with HttpClient4; with the Java implementation connection reuse is not under JMeter’s control.

Use multipart/form-data

HTTPSampler.DO_MULTIPART_POST

cleared

Sends the parameters as multipart/form-data instead of application/x-www-form-urlencoded. Since 3.1 only POST supports multipart requests and file upload.

Browser-compatible headers

HTTPSampler.BROWSER_COMPATIBLE_MULTIPART

cleared

With multipart, suppresses the per-part Content-Type and Content-Transfer-Encoding headers and sends only Content-Disposition, as browsers do.

Parameters tab

HTTPsampler.Arguments (HTTPArgument rows)

empty table

Rows of Name, Value, URL Encode?, Content-Type and Include Equals?. For GET and DELETE they become the query string; for POST and PUT they are sent in the body (form-encoded, or multipart). A row without a name is ignored for methods other than POST and PUT, which is a way to make a parameter optional through a variable.

Body Data tab

HTTPSampler.postBodyRaw = true and one unnamed argument

empty

The raw request body, typically JSON or XML. Each line is sent with CRLF appended except the last. Available when the request has only unnamed parameters or none; switching from Parameters converts them, and switching back is only possible after clearing the body.

Files Upload tab

HTTPsampler.Files (HTTPFileArg rows: File.path, File.paramname, File.mimetype)

empty table

Files to send, with File Path, Parameter Name and MIME Type. A file makes the request multipart. For POST, PUT and PATCH, a single file with an empty parameter name is sent as the whole body, with MIME Type as its Content-Type. See File upload.

Advanced tab: client, timeouts, resources, source address and proxy

GUI field .jmx property Default Meaning

Client implementation: Implementation

HTTPSampler.implementation

empty

HttpClient4, Java, or empty. Empty uses the HTTP Request Defaults, then the jmeter.httpsampler property, then HttpClient4. See HttpClient4 or Java.

Timeouts (milliseconds): Connect

HTTPSampler.connect_timeout

empty (no timeout)

Milliseconds to wait for a connection to open. A failed connect is recorded with a connect time equal to the time to fail.

Timeouts (milliseconds): Response

HTTPSampler.response_timeout

empty (no timeout)

Milliseconds to wait for a response. It applies to each wait for data: a response sent in several chunks can take longer overall than the timeout. Use a Duration Assertion to fail slow responses as a whole (see Assertions).

Retrieve All Embedded Resources

HTTPSampler.image_parser

cleared

Parses HTML responses and downloads the images, scripts, style sheets (and resources referenced from them), frames and background images they reference, as sub-samples of this request.

Parallel downloads. Number

HTTPSampler.concurrentDwn, HTTPSampler.concurrentPool

cleared, 6

Downloads embedded resources over a pool of concurrent connections of the given size, as browsers do.

URLs must match

HTTPSampler.embedded_url_re

empty

If set, a regular expression that embedded URLs must match to be downloaded (for example only resources from the application’s own host).

URLs must not match

HTTPSampler.embedded_url_exclude_re

empty

If set, a regular expression for embedded URLs to skip (for example images, or third-party analytics).

Source address: type and value

HTTPSampler.ipSourceType, HTTPSampler.ipSource

IP/Hostname, empty

HttpClient implementation only. Overrides the local address the request is sent from ("IP spoofing"); the JMeter host needs several addresses. The type is IP/Hostname, Device (first address of an interface such as eth0), Device IPv4 or Device IPv6. The property httpclient.localaddress sets one address for all HttpClient requests instead.

Proxy Server: Scheme, Server Name or IP, Port Number, Username, Password

HTTPSampler.proxyScheme, proxyHost, proxyPort, proxyUser, proxyPass (all with the HTTPSampler. prefix)

empty

A proxy for this request, without http:// in the host. The port is required when a host is given. The password is stored unencrypted in the .jmx file, so read it from a property (${__P(proxyPass)}) instead of typing it.

Optional Tasks: Save response as MD5 hash?

HTTPSampler.md5

cleared

Stores the 32-character MD5 hash of the response instead of the body. Intended for large responses; it saves memory but makes the body unavailable to extractors and content assertions.

The Proxy Server panel’s Scheme field and the Optional Tasks grouping are labels of the 5.6.3 GUI (resource bundle messages.properties); the component reference table lists the other proxy fields and the MD5 option without those headings.

Parameters, Body Data and content encoding

The method decides where parameters go. The table summarises the rules of the component reference’s "Parameter Handling" and "Method Handling" notes.

Method Parameters tab Body

GET, DELETE

Appended to the URL as the query string.

DELETE may carry a body through Body Data; GET normally does not.

POST

Sent in the body, form-encoded or (with Use multipart/form-data or files) multipart.

Body Data, unnamed parameter values concatenated, or a single file without a parameter name.

PUT, PATCH

Not used as form parameters: the body must be given another way.

Body Data, unnamed parameter values, or a single file with an empty parameter name.

GET, DELETE, PUT and PATCH requests that carry a body need a Content-Type: add a Header Manager under the sampler and set it there. For POST, the property http.post_add_content_type_if_missing decides whether JMeter adds application/x-www-form-urlencoded when the header is missing; it defaults to false since 5.0 (Bug 60190).

URL encoding. Selecting URL Encode? on a parameter is almost always right: values with spaces, non-ASCII characters or reserved characters such as &, =, +, ? or / must be encoded. A value that is already encoded (copied from a browser address bar) must not be encoded twice.

Content encoding. Since 5.6.1 an empty Content encoding means UTF-8: parameter names, values and file names with Unicode characters are sent correctly without setting anything. The property sampleresult.default.encoding, which decides how a response without a charset is decoded, also defaults to UTF-8 since 5.6.1. Plans written for older versions sometimes set UTF-8 explicitly; that is harmless.

A search with a non-ASCII term on the Bookshelf API, written with parameters:

<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /api/books (search)" enabled="true">
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
    <collectionProp name="Arguments.arguments">
      <elementProp name="query" elementType="HTTPArgument">
        <boolProp name="HTTPArgument.always_encode">true</boolProp>
        <stringProp name="Argument.name">query</stringProp>
        <stringProp name="Argument.value">${term}</stringProp>
        <stringProp name="Argument.metadata">=</stringProp>
        <boolProp name="HTTPArgument.use_equals">true</boolProp>
      </elementProp>
    </collectionProp>
  </elementProp>
  <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>
</HTTPSamplerProxy>
<hashTree/>

With term set to García Márquez (from search-terms.csv, see Parameterization & Test Data), the request line is GET /api/books?query=Garc%C3%ADa+M%C3%A1rquez.

The same API’s POST /api/loans takes a JSON body. With Body Data the sampler stores the body as one unnamed argument and sets HTTPSampler.postBodyRaw; the Content-Type comes from a Header Manager placed under the sampler:

<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /api/loans" enabled="true">
  <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">{"bookId": ${bookId}, "days": 14}</stringProp>
        <stringProp name="Argument.metadata">=</stringProp>
      </elementProp>
    </collectionProp>
  </elementProp>
  <stringProp name="HTTPSampler.path">/api/loans</stringProp>
  <stringProp name="HTTPSampler.method">POST</stringProp>
  <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
  <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
</HTTPSamplerProxy>
<hashTree>
  <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="Content-Type JSON" enabled="true">
    <collectionProp name="HeaderManager.headers">
      <elementProp name="" elementType="Header">
        <stringProp name="Header.name">Content-Type</stringProp>
        <stringProp name="Header.value">application/json</stringProp>
      </elementProp>
    </collectionProp>
  </HeaderManager>
  <hashTree/>
</hashTree>

The Authorization header the loan endpoint also needs, and the extraction of bookId and the token from earlier responses, are covered on the sessions page and on Correlation & Extractors.

Redirects

Two check boxes control redirects, and they behave very differently:

Follow Redirects (default) Redirect Automatically

Who follows

JMeter’s sampler code.

The underlying HTTP library.

Visible in results

Yes: each hop is a sub-sample of the parent sample (label-0, label-1 and so on since 5.0).

No: only the final response is seen.

Cookies and headers on later hops

Handled by JMeter for every hop (Cookie Manager, Header Manager).

Sent only with the first request.

Methods

All.

GET and HEAD only (HttpClient rejects POST and PUT).

Timing

Parent elapsed time and bytes include all hops; latency comes from the first response.

One sample with the library’s total time.

Related properties: httpsampler.max_redirects (default 20) limits the redirects followed in one sequence, and JMeter collapses /../segment in redirect URLs unless httpsampler.redirect.removeslashdotdot=false. The message "Redirect requested but followRedirects is disabled" in the log can be ignored. For REST APIs a redirect is usually a bug or a missing trailing slash; keep Follow Redirects on and assert on the final status, or switch it off and assert that the 3xx itself is returned when the redirect is the behaviour under test.

The 5.6.3 sampler handles 307 and 308 like the other redirects. Preserving the original method on 307 and 308 (as the HTTP specification requires) is a change planned for 6.0 in changes.xml on the master branch.

Keep-alive and connection reuse

With Use KeepAlive and HttpClient4, a thread reuses its connection from one request to the next, as a browser or an HTTP client library in a microservice would. The server then sees a fixed number of long-lived connections (one or a few per virtual user) and most samples show a connect time of 0. Without keep-alive every request opens a new TCP connection and, with HTTPS, performs a new TLS handshake: that measures connection setup as much as the API, costs CPU on both sides and can exhaust ephemeral ports on the load generator.

Connection lifetime is tuned with properties of the HttpClient4 implementation:

Property Default Meaning

httpclient4.time_to_live

60000

Absolute TTL of a connection in milliseconds; it is never reused beyond it.

httpclient4.validate_after_inactivity

4900

Check a connection that has been idle longer than this before reusing it. Keep it at least 150 ms below httpclient4.time_to_live.

httpclient4.idletimeout

0

Idle timeout applied when the server sends no Keep-Alive timeout header.

httpclient4.retrycount

0

Retries for idempotent methods; httpclient4.request_sent_retry_enabled=true retries all methods.

httpclient.reset_state_on_thread_group_iteration

true

Closes connections and resets the SSL state at the start of each Thread Group iteration, so each iteration behaves as a new user. The Thread Group’s Same user on each iteration option controls the same behaviour per group.

Retries hide errors: a request that fails once and succeeds on retry is recorded as one successful sample. Keep the retry count at 0 for load tests unless a load balancer that drops idle connections makes it unavoidable.

File upload

Files go in the Files Upload tab: one row per file with File Path (relative to the .jmx directory, or absolute), Parameter Name (the form field name) and MIME Type (guessed from the file when empty). With at least one file the request becomes multipart/form-data; any other parameters become parts of the same form. Multipart and file upload work with POST only. For PUT or PATCH, or for a POST whose body is just the file, leave Parameter Name empty: the file is then sent as the whole body, with MIME Type as Content-Type.

The Bookshelf API has no upload endpoint; the fragment below shows the shape for a cover-image upload of the kind many APIs expose (POST /api/covers, a field named cover and a bookId form field), to adapt to a real endpoint of the system under test:

<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /api/covers" enabled="true">
  <elementProp name="HTTPsampler.Files" elementType="HTTPFileArgs">
    <collectionProp name="HTTPFileArgs.files">
      <elementProp name="data/cover.png" elementType="HTTPFileArg">
        <stringProp name="File.path">data/cover.png</stringProp>
        <stringProp name="File.paramname">cover</stringProp>
        <stringProp name="File.mimetype">image/png</stringProp>
      </elementProp>
    </collectionProp>
  </elementProp>
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
    <collectionProp name="Arguments.arguments">
      <elementProp name="bookId" elementType="HTTPArgument">
        <boolProp name="HTTPArgument.always_encode">false</boolProp>
        <stringProp name="Argument.name">bookId</stringProp>
        <stringProp name="Argument.value">${bookId}</stringProp>
        <stringProp name="Argument.metadata">=</stringProp>
      </elementProp>
    </collectionProp>
  </elementProp>
  <stringProp name="HTTPSampler.path">/api/covers</stringProp>
  <stringProp name="HTTPSampler.method">POST</stringProp>
  <boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp>
  <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp>
  <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
  <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
</HTTPSamplerProxy>
<hashTree/>

Load-generator cost of uploads. JMeter reads the file for every request, so 50 threads uploading a 2 MB file at one upload per second each send about 100 MB/s, which saturates a 1 Gbit/s network link (about 125 MB/s) before the server does anything. Keep files small, or make sure the network and disk of the generator are part of the capacity check. In a distributed run, the files must exist on every load generator, like CSV data files.

Embedded resources

Retrieve All Embedded Resources makes JMeter behave a little like a browser for HTML pages: it parses each HTML (and CSS) response and fetches the referenced resources. The parsers are configured with HTTPResponse.parsers (default htmlParser wmlParser cssParser); the HTML parser is LagartoBasedHtmlParser by default (htmlParser.className), and httpsampler.max_frame_depth (default 5) limits frame nesting. Resources are recorded as sub-samples of the page; httpsampler.ignore_failed_embedded_resources=true stops a failed resource from failing the parent, and httpsampler.embedded_resources_use_md5=true keeps only their size and MD5.

Filters keep the download to what a real browser of the application would fetch from the system under test. To fetch only resources from the Bookshelf host and skip images in any case:

URLs must match:      https?://localhost(:8080)?/.*
URLs must not match:  .*\.(?i:svg|png|jpe?g|gif|ico)

For a JSON API, as Bookshelf is, leave the option off: there is nothing to parse, and parsing costs CPU. JMeter has no browser cache unless an HTTP Cache Manager is added, and it never executes JavaScript, so resources loaded by scripts are not fetched.

Load-generator cost. Each page with embedded resources becomes many requests: a page with 30 resources and 50 users is 1 550 requests per page view. With Parallel downloads each thread uses up to the pool size (default 6) extra download threads, so 50 users can mean 300 extra threads on the generator; their keep-alive time is httpsampler.parallel_download_thread_keepalive_inseconds (default 60).

HttpClient4 or Java

HttpClient4 Java

Library

Apache HttpComponents HttpClient 4.x (4.5.14 in 5.6.x).

The JVM’s built-in HTTP client.

Connection reuse

Controlled: keep-alive, TTL, validation properties.

No control; settings are JVM-wide system properties.

Methods

All listed above, WebDAV and user-defined methods.

GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE only (no PATCH).

Client certificates (Keystore Configuration)

Supported.

Not supported.

Kerberos, virtual hosts, DNS Cache Manager, source address

Supported.

Not supported.

Retries

httpclient4.retrycount (default 0).

http.java.sampler.retries (default 0).

Bandwidth emulation

httpclient.socket.http.cps and httpclient.socket.https.cps.

HTTPS only.

Response size

Includes transport overhead such as chunk headers.

Body only.

HttpClient4 is the default and the right choice for load testing; there is little reason to use Java today. The implementation can be set per sampler, once in the HTTP Request Defaults, or globally:

# user.properties: default implementation for samplers that leave it empty
jmeter.httpsampler=HttpClient4

The component reference contradicts itself on the fallback: the HTTP Request entry says HttpClient4 is used when nothing is set, the HTTP Request Defaults entry says Java. The 5.6.3 source (HTTPSamplerFactory) reads jmeter.httpsampler with HttpClient4 as its default, and the properties reference gives the same default, so HttpClient4 is what runs.

HTTPS

HTTPS needs nothing more than https in Protocol (and usually port 443 or 8443). JMeter uses the TLS implementation of the JVM (JSSE), so the protocol versions and cipher suites available are those of the Java runtime that runs JMeter.

Self-signed and untrusted server certificates

The getting-started guide states that the JMeter HTTP samplers accept all server certificates, trusted or not and whatever their validity period, to allow maximum flexibility when testing. A Bookshelf API on https://localhost:8443 with a self-signed certificate therefore works without importing anything into a trust store. This is convenient and also means a load test never detects an expired or wrong certificate: check certificates with a functional test, not with JMeter.

The one certificate error JMeter can still raise comes from the JVM’s algorithm constraints: a server certificate (or one in its chain) signed with MD2, or with a key shorter than 1024 bits, fails with "Certificates does not conform to algorithm constraints". The component reference describes relaxing jdk.certpath.disabledAlgorithms in the JVM’s java.security file; on a test environment the better fix is a proper certificate.

TLS protocol and SSL properties

JMeter property Default Meaning

https.default.protocol

TLS

Protocol level of the SSL context. The default depends on the JVM; change it only when a server requires a specific level.

https.socket.protocols

JVM default

Space-separated list of protocols to enable, for servers that do not negotiate (errors such as SSLPeerUnverifiedException: peer not authenticated or Connection reset).

https.cipherSuites

JVM default

Comma-separated cipher suites to offer: to match the clients being simulated, or to reduce JMeter’s encryption cost with many users.

https.sessioncontext.shared

false

SSL session contexts are per thread (each virtual user negotiates its own sessions, like separate browsers). true shares one context, which is not how real clients behave.

httpclient.reset_state_on_thread_group_iteration

true

Resets the SSL state (and closes connections) at each Thread Group iteration. Set false, or select Same user on each iteration, when one user iterates several times.

https.use.cached.ssl.context

true

Deprecated in favour of the property above, but still checked by the Keystore Configuration (see below).

https.keyStoreStartIndex, https.keyStoreEndIndex

0, 0

First and last alias index used from a keystore with many entries.

A user.properties restricting JMeter to TLS 1.2 and 1.3 for a server that does not negotiate correctly:

https.default.protocol=TLS
https.socket.protocols=TLSv1.2 TLSv1.3

The Java (JSSE) system properties do not go in user.properties. Since JMeter no longer converts javax.* entries of jmeter.properties into system properties, they belong in bin/system.properties or on the command line as -D options. system.properties contains commented examples of javax.net.ssl.trustStore, javax.net.ssl.keyStore, their passwords, types and providers, and of javax.net.debug, which prints the TLS handshake when a connection fails:

jmeter -n -t bookshelf-load.jmx -l tls-debug.jtl -Jthreads=1 -Jloops=1 -Jport=8443 -Djavax.net.debug=ssl:handshake

Turn handshake debugging on for a single-user run only: it writes a large amount of output per connection.

Client certificates: Keystore Configuration

When the server requires mutual TLS, each request must present a client certificate. JMeter loads the certificates from a Java keystore given by JSSE system properties, and the Keystore Configuration element (Add → Config Element → Keystore Configuration) controls which keys are loaded and which one each thread uses. Loading the keystore up front keeps its initialisation out of the response times.

GUI field .jmx property Default Meaning

Name

testname attribute

Keystore Configuration

Descriptive name.

Preload

preload

true

Load the keystore before the test starts. Required; true is usually the best option.

Variable name holding certificate alias

clientCertAliasVarName

empty

Name of a variable (filled, for example, from a CSV Data Set) holding the alias each thread uses. Optional.

Alias Start Index

startIndex

0

Index of the first key to use, 0-based. Required.

Alias End Index

endIndex

-1

Index of the last key to use, 0-based; -1 loads all. With an alias variable, make it large enough for all keys to be loaded at startup. Required.

To use more than one certificate, the component reference requires https.use.cached.ssl.context=false in user.properties and the HttpClient4 implementation; the 5.6.3 code logs a warning when that property is not false. The Java implementation does not support client certificates at all.

A setup for the Bookshelf API behind mTLS, with one certificate per virtual user. The keystore bookshelf-clients.p12 holds aliases user001 to user050, and users.csv gains a certAlias column:

# user.properties
https.use.cached.ssl.context=false
<KeystoreConfig guiclass="TestBeanGUI" testclass="KeystoreConfig" testname="Keystore Configuration" enabled="true">
  <stringProp name="preload">true</stringProp>
  <stringProp name="clientCertAliasVarName">certAlias</stringProp>
  <stringProp name="startIndex">0</stringProp>
  <stringProp name="endIndex">-1</stringProp>
</KeystoreConfig>
<hashTree/>

The keystore and its password are passed as system properties. The password is read from an environment variable of the CI job or shell, never written in the plan or in a committed file:

jmeter -n -t bookshelf-load.jmx -l mtls.jtl -Jport=8443 \
  -Djavax.net.ssl.keyStore=bookshelf-clients.p12 \
  -Djavax.net.ssl.keyStoreType=PKCS12 \
  -Djavax.net.ssl.keyStorePassword="$BOOKSHELF_KEYSTORE_PASSWORD"

For a hardware token, the component reference describes setting javax.net.ssl.keyStoreType to PKCS11 and javax.net.ssl.keyStore to NONE.

SSL Manager

The SSL Manager is the GUI way to choose a client certificate: Options → SSL Manager opens a file chooser; a file with the .p12 extension is read as PKCS12, anything else as a JKS keystore, and JMeter asks for the password (which it assumes is also the private key’s password). If the keystore has several keys, the first is used, and there is no way to choose another. Because it is interactive, the SSL Manager is useful for trying a certificate while building a plan; CLI runs use the system properties and the Keystore Configuration shown above. The same section of the reference explains that a private CA can be trusted for the whole JVM by importing it into a jssecacerts file in the JRE’s lib/security folder, which is read before cacerts; JMeter’s own HTTP samplers do not need it, since they accept all server certificates.

DNS Cache Manager

By default JMeter resolves host names through the JVM, which caches the result. Behind a DNS-based load balancer or CDN, every virtual user then talks to the same address and only one server of the cluster gets the load. The DNS Cache Manager (Add → Config Element → DNS Cache Manager) resolves names separately for each thread and keeps the result in its own cache, independent of the JVM and operating system caches. It can also map names to addresses like an /etc/hosts file, so a plan can use the real host name while the requests go to a test server.

It belongs at the root of the Thread Group or Test Plan, never under a single sampler, and it works only with the HttpClient4 implementation.

GUI field .jmx property Default Meaning

Name

testname attribute

DNS Cache Manager

Descriptive name.

Clear cache each iteration

DNSCacheManager.clearEachIteration

selected

Clears every thread’s DNS cache at the start of each iteration, so each iteration resolves again.

Use system DNS resolver

DNSCacheManager.isCustomResolver = false

selected

Uses the system resolver. For it to re-resolve, the component reference says to add networkaddress.cache.ttl=0 to the JVM’s java.security file.

Use custom DNS resolver

DNSCacheManager.isCustomResolver = true

cleared

Uses the dnsjava library as resolver. Required for the static host table.

DNS Servers table (Hostname or IP address, Add, Delete)

DNSCacheManager.servers

empty

DNS servers the custom resolver queries; empty means the network configuration’s DNS.

Static Host Table (Host, Hostname or IP address, Add static host, Delete static host)

DNSCacheManager.hosts (StaticHost.Name, StaticHost.Address)

empty

Maps a host name to another host name or IP address, resolved with the custom resolver. Static entries win over the resolver.

A DNS Cache Manager that sends requests for bookshelf.example.test to the local Compose stack, while the requests keep Host: bookshelf.example.test (useful when a reverse proxy routes on the host name):

<DNSCacheManager guiclass="DNSCachePanel" testclass="DNSCacheManager" testname="DNS Cache Manager" enabled="true">
  <collectionProp name="DNSCacheManager.servers"/>
  <collectionProp name="DNSCacheManager.hosts">
    <elementProp name="bookshelf.example.test" elementType="StaticHost">
      <stringProp name="StaticHost.Name">bookshelf.example.test</stringProp>
      <stringProp name="StaticHost.Address">127.0.0.1</stringProp>
    </elementProp>
  </collectionProp>
  <boolProp name="DNSCacheManager.clearEachIteration">true</boolProp>
  <boolProp name="DNSCacheManager.isCustomResolver">true</boolProp>
</DNSCacheManager>
<hashTree/>

Run it with -Jhost=bookshelf.example.test. Per-thread resolution costs one DNS lookup per thread (and per iteration with Clear cache each iteration): with 50 threads and iterations of a few seconds that is tens of lookups per second against the DNS server, which is part of the load.

Bandwidth throttling

A load generator on the same network as the server downloads responses far faster than users on mobile networks do, which keeps connections short and hides the effect of slow clients on server threads and connection pools. JMeter can emulate slow connections with two properties, in characters (bytes) per second; 0, the default, disables throttling:

# user.properties: emulate about 1 Mbit/s per connection (1 000 000 / 8 = 125 000 bytes/s)
httpclient.socket.http.cps=125000
httpclient.socket.https.cps=125000

Worked numbers: a Bookshelf search response of 24 KB takes about 24 000 / 125 000 = 0.19 s to read at that rate, on top of the server time. With 50 threads and a server time of 50 ms, each thread completes a request about every 0.24 s instead of every 0.05 s, so the closed-model throughput falls from about 1 000 to about 210 requests per second, while the server holds about four times as many open responses at any moment. The HttpClient4 implementation supports both properties; the Java implementation only the HTTPS one.

Bandwidth throttling with these properties is the technique described in the JMeter Cookbook (chapter 8) and Pro Apache JMeter (chapter 10); it is unchanged in 5.6.3.

HTTP/2

Core JMeter speaks HTTP/1.1 (httpclient.version defaults to 1.1); it has no HTTP/2 support. HTTP/2 load comes from the community BlazeMeter - HTTP Plugin (Plugins Manager id bzm-http2, version 3.2.0 in the catalogue, covering HTTP/1.1, HTTP/2 and HTTP/3; the older Java 8 bzm-http2-legacy is deprecated), which adds its own sampler. It is installed through the Plugins Manager like any plugin, and is covered with the other plugins later in this section. jmeter-plugins.org is an independent community catalogue, not an Apache project.

Worked example: the first Bookshelf test in CLI mode

The plan built above, bookshelf-load.jmx, now runs as a smoke test: one user, ten iterations of GET /api/books?query=java. GUI mode is for building and debugging; tests run in CLI mode.

  1. Start the Bookshelf API (docker compose up -d in its project) and check it answers:

    curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost:8080/api/books?query=java'

    which prints 200.

  2. Run the plan in CLI mode with its property defaults (1 thread, 1 s ramp-up, 10 loops, localhost:8080):

    jmeter -n -t bookshelf-load.jmx -l smoke.jtl -j smoke.log

    Representative output (numbers depend on the machine and on the API):

    Creating summariser <summary>
    Created the tree successfully using bookshelf-load.jmx
    Starting standalone test @ 2026 Oct 14 10:00:00 CEST (1791964800000)
    Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
    summary =     10 in 00:00:01 =    8.1/s Avg:    58 Min:    21 Max:   247 Err:     0 (0.00%)
    Tidying up ...    @ 2026 Oct 14 10:00:02 CEST (1791964802000)
    ... end of run
  3. Inspect the results file. smoke.jtl is CSV with the default 5.6.3 columns:

    head -4 smoke.jtl
    timeStamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect
    1791964800412,247,GET /api/books,200,OK,Borrowers 1-1,text,true,,2384,163,1,1,http://localhost:8080/api/books?query=java,245,0,14
    1791964800661,31,GET /api/books,200,OK,Borrowers 1-1,text,true,,2384,163,1,1,http://localhost:8080/api/books?query=java,30,0,0
    1791964800693,24,GET /api/books,200,OK,Borrowers 1-1,text,true,,2384,163,1,1,http://localhost:8080/api/books?query=java,23,0,0

Reading the rows:

  • The first request took 247 ms and has Connect 14: a new TCP connection, plus the cold start of the API (first query, JIT). Later requests reuse the connection (Connect 0, keep-alive) and take 20 to 30 ms. A smoke run is too short to measure anything; its purpose is to prove the plan works end to end before adding load.

  • Latency is close to elapsed because the JSON response is small and arrives in one piece.

  • success is true because the status is 200: HTTP responses in the 4xx and 5xx ranges are normally treated as failures without any assertion. A 200 with an error message in the body is still a success until an assertion checks the content, which is the subject of Assertions.

  • bytes (2 384) is the response size including headers, sentBytes (163) the request size.

The same plan then runs the load profile used across the section (50 threads, a 60 s ramp-up, held for 10 minutes) once the Thread Group is switched to a lifetime of ${__P(duration,660)} seconds with infinite loops, as shown on the thread groups page:

jmeter -n -t bookshelf-load.jmx -l load.jtl -e -o report-load \
  -Jthreads=50 -Jrampup=60 -Jloops=-1 -Jduration=660

Worked numbers for this profile: 50 threads over 60 s start 0.83 threads per second. With no think time and a 25 ms response, each thread could send about 40 requests per second, so 50 threads would aim at about 2 000 requests per second against one endpoint, which measures the API’s ceiling, not realistic use. With the 3 s average think time the Bookshelf journey uses (see Timers & Think Time), each thread sends 1 / (3 + 0.025) ≈ 0.33 requests per second, about 16.5 requests per second for 50 users. Adding think time before any real run is the next step after this smoke test. Running tests in CLI mode, the results file and the HTML dashboard are covered in detail later in this section.

Load-generator cost of HTTP requests

The HTTP sampler is cheap per request compared with the server’s work, but a few options multiply its cost:

  • Response bodies in memory. Every sample keeps its full response until post-processors, assertions and listeners have used it. Large downloads with many threads consume heap; httpsampler.max_bytes_to_store_per_request truncates what is stored (default 0, no truncation) and Save response as MD5 hash? keeps only a hash. Extractors and content assertions then see the truncated data or none.

  • Embedded resources and parallel downloads. Many requests and extra threads per page view (see above).

  • TLS without keep-alive. A full handshake per request is CPU-intensive on the generator as well as on the server. Per-thread SSL contexts are realistic; a reset per iteration (httpclient.reset_state_on_thread_group_iteration=true) adds a handshake per iteration and user.

  • Per-thread DNS. The DNS Cache Manager adds lookups per thread and iteration.

  • Listeners that keep responses. View Results Tree in a load test keeps every response in memory; the listeners page explains which listeners are safe.

The JMeter best practices are to run load in CLI mode, with few listeners and assertions and results saved as CSV; they apply to every HTTP plan.

Pitfalls

  • Host or port typed in every sampler. Moving to another environment then means editing every request. Put them in HTTP Request Defaults, read from properties.

  • Expecting the Defaults path to be a prefix. It is a full default path; use a variable for a prefix.

  • Variable values in sampler names. GET /api/books/${id} as a name creates one report row per ID; keep the variable in the Path and a fixed name such as GET /api/books/{id}.

  • Redirect Automatically on a POST. HttpClient rejects it; use Follow Redirects.

  • A body without Content-Type. PUT, PATCH and bodies on GET or DELETE need a Header Manager setting it; servers answer 415 Unsupported Media Type otherwise.

  • Double encoding. Selecting URL Encode? on a value that is already percent-encoded encodes it twice.

  • No timeouts. Without connect and response timeouts a hung server keeps threads waiting forever and the closed model stalls; set them in the defaults.

  • Retries switched on. They hide errors and distort response times.

  • Treating HTTP success as functional success. A 200 is not proof that the right data came back: add assertions.

  • Embedded resources against an API or a CDN. Parsing JSON for resources wastes CPU; fetching third-party resources loads systems you do not own. Filter with URLs must match.

  • Testing certificates with JMeter. The samplers accept every server certificate, so certificate problems go unnoticed.

  • Proxy passwords and keystore passwords in the plan. The proxy password is stored unencrypted; read secrets from properties or environment variables.

From the books to 5.6 to 6.0

Topic Books (JMeter 2.11 to 3.0: Cookbook ch. 8, Pro ch. 3, 5 and 10) JMeter 5.6.3 and planned 6.0

Implementations

HttpClient3.1, HttpClient4 and Java.

HttpClient3.1 deprecated in 3.0 and removed in 3.2; only HttpClient4 (the default) and Java remain.

Multipart and file upload

Multipart for POST and PUT.

Since 3.1 only POST supports multipart and file upload; a single file without a parameter name is the raw body of a POST, PUT or PATCH.

Encoding

Platform or ISO-8859-1 defaults; UTF-8 usually set by hand.

UTF-8 is the default request encoding and sampleresult.default.encoding since 5.6.1.

Content-Type on POST

Added automatically when missing.

http.post_add_content_type_if_missing defaults to false since 5.0 (Bug 60190).

Running tests

"Non-GUI mode".

Called CLI mode; GUI mode is for building plans only.

Bandwidth throttling

httpclient.socket.http.cps and httpclient.socket.https.cps.

Unchanged.

DNS, client certificates

DNS Cache Manager and Keystore Configuration available.

Unchanged in purpose; the Keystore Configuration loads all aliases by default (endIndex -1) and supports PKCS11.

HTTP/2

Not available.

Still not in core; bzm-http2 plugin.

JMeter 6.0 (planned, changes.xml on master)

Not applicable.

Requires Java 17 or later. HTTP-related planned changes: 307 and 308 redirects keep the original method; no automatic ; charset= on multipart/form-data; individual HTTP request arguments can be enabled or disabled in the GUI; Internet Explorer conditional comments are skipped when fetching resource links; the Cookie Manager stops sending expired cookies; dnsjava (used by the DNS Cache Manager) updated to 3.6.x.

References