HTTP Sessions, Headers, Cookies & Authentication

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. No plugin is needed: the four managers and the URL Re-writing Modifier ship with core JMeter. The HTTP Request sampler implementations discussed are the two that remain in 5.6.3, HttpClient4 (Apache HttpComponents 4.5.x) and Java. The books on the market target JMeter 2.11 to 3.0; the managers they describe still exist, but defaults such as the cookie policy and pre-emptive authentication have changed since (listed at the end of the page). Every field name, property and rule in this page is taken from the official component reference, properties reference and user manual, and, where the documentation is ambiguous, from the 5.6.3 source code of the Apache JMeter project.

A browser keeps state between requests without being asked: it stores the cookies a server sets and sends them back, it remembers which resources it already has, it adds the same headers to every request and it answers authentication challenges. JMeter does none of that unless the test plan says so. An HTTP Request sampler on its own sends exactly what it is configured with, and every thread starts from nothing. The managers are configuration elements that give each virtual user that browser-like memory: the HTTP Header Manager (headers), the HTTP Cookie Manager (cookies), the HTTP Cache Manager (conditional and cached GETs) and the HTTP Authorization Manager (server authentication). For applications that carry the session in the URL instead of a cookie, the HTTP URL Re-writing Modifier does the same job.

This page describes each element field by field, then explains how several of them in scope are combined, and ends with a complete worked example for the Bookshelf API: log in once per virtual user, extract the JWT from the login response and send it as a bearer token on every later request. It builds on Test Plan Elements, Execution Order & Scoping and Properties, Variables & Functions, and pairs with HTTP Requests. The OAuth and JWT background (what a bearer token is, how it is obtained and refreshed) is in the OAuth Reference and is not repeated here.

What "a session" means for a JMeter thread

Each JMeter thread is one virtual user, and everything a manager stores is stored per thread. The table lists the pieces of state a real browser session has and the element that reproduces each one.

Browser behaviour JMeter element Per thread?

Sends the same headers on every request (Accept, User-Agent, Authorization)

HTTP Header Manager

The header definitions are shared by all threads; the values are resolved per thread when they contain variables such as ${token}.

Stores cookies set by the server and sends them back

HTTP Cookie Manager

Yes: each thread has its own cookie storage area. Cookies typed into the manager’s table are shared by all threads.

Reuses cached responses and sends If-None-Match / If-Modified-Since

HTTP Cache Manager

Yes: each thread has its own cache (5000 entries by default).

Answers a 401 challenge for Basic, Digest or Kerberos authentication

HTTP Authorization Manager

The credential table is shared; use variables to give each thread its own user. Kerberos subjects are kept per thread.

Keeps a session id in the URL (;jsessionid=...)

HTTP URL Re-writing Modifier

Yes: the id is read from each thread’s previous response.

Remembers a token returned in a JSON body

A post-processor (JSON Extractor) plus a variable

Yes: variables are per thread.

Bearer tokens such as a JWT are not managed by any manager: they are ordinary values extracted from a response into a variable and sent back through a Header Manager. That combination is the worked example at the end of this page.

The managers are not samplers: they never send a request. A sampler that has a manager in scope asks it for headers, cookies, cache entries or credentials just before it sends the request, and hands it the response afterwards:

flowchart LR S["HTTP Request sampler"] -->|"1 headers to add"| HM["HTTP Header Manager"] S -->|"2 cookies for this URL"| CM["HTTP Cookie Manager"] S -->|"3 cached entry? conditional headers"| CA["HTTP Cache Manager"] S -->|"4 credentials for this URL"| AM["HTTP Authorization Manager"] S --> SRV[("Bookshelf API")] SRV -->|"response"| S S -->|"5 Set-Cookie"| CM S -->|"6 Last-Modified, ETag, Cache-Control"| CA

Where the managers sit in the test plan

All four managers are configuration elements, added with a right-click on the Thread Group (or on a controller or a sampler) and Add → Config Element → HTTP Header Manager (or HTTP Cookie Manager, HTTP Cache Manager, HTTP Authorization Manager). The HTTP URL Re-writing Modifier is a pre-processor: Add → Pre Processors → HTTP URL Re-writing Modifier. They follow the normal scoping rules: an element applies to every sampler in the branch it is placed in. The user manual’s advanced web test plan chapter recommends adding the Header Manager, like the Cookie Manager, at Thread Group level unless different requests need different headers.

The skeleton used on every HTTP page of this section:

Test Plan  bookshelf-load.jmx
├── HTTP Request Defaults            (${__P(host,localhost)}:${__P(port,8080)})
└── Thread Group "Borrowers"         (${__P(threads,1)} threads, ${__P(rampup,1)} s ramp-up)
    ├── HTTP Header Manager          Accept and Content-Type: application/json
    ├── HTTP Cookie Manager          one per Thread Group
    ├── HTTP Cache Manager           only when caching behaviour matters
    ├── CSV Data Set Config          users.csv: username,password
    ├── Once Only Controller "Login once per user"
    │   └── POST /api/auth/login     + JSON Extractor (token)
    └── Simple Controller "Authenticated journey"
        ├── HTTP Header Manager      Authorization: Bearer ${token}
        ├── GET  /api/books?query=...
        ├── GET  /api/books/{id}
        ├── POST /api/loans
        └── GET  /api/loans/{id}

HTTP Header Manager

The Header Manager lets a plan add or override HTTP request headers. Typical uses for an API test are the content negotiation headers (Accept: application/json and Content-Type: application/json for JSON bodies), an Authorization: Bearer ... header built from an extracted token, correlation or tenant headers expected by the service, and a realistic User-Agent.

Fields

Field Meaning Required

Name

Descriptive name shown in the tree.

No

Name (Header)

Name of the request header. Matching between managers is case-insensitive (see below).

No (at least one row is expected)

Value

Request header value. Variables and functions are allowed and resolved for each request in the context of the sampler that uses the manager (the functions reference states that config elements referenced by samplers, such as the Header and Cookie managers, resolve functions in the context of the HTTP sampler).

No (at least one row is expected)

The table is titled Headers Stored in the Header Manager and has five buttons:

Button Action

Add

Adds an empty row.

Add from Clipboard

Splits the clipboard into lines and each line on the first : (or a tab) into name and value, so headers copied from browser developer tools or a cURL command can be pasted in one go.

Delete

Deletes the selected row.

Load

Loads a previously saved header table and adds its rows to the existing ones.

Save

Saves the header table to a file.

In the .jmx file each row is a Header element:

<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="JSON headers">
  <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/>

Several Header Managers: merged in 5.6.3

The official documentation contradicts itself here. The Elements of a Test Plan chapter of the user manual says that the Header Manager, Cookie Manager and Authorization Manager are not merged: if more than one is in scope only one is used, with no way to say which. The HTTP Header Manager entry of the component reference says that JMeter supports multiple Header Managers, that their entries are merged to form the list for the sampler, and that an entry whose name matches an existing header replaces it.

The component reference describes the 5.6.3 behaviour. In the 5.6.3 source, the HTTP sampler merges every Header Manager it receives into the one it already has (HeaderManager.merge), while for the Cookie, Cache and Authorization managers it keeps only one and logs a warning that the existing manager was superseded. The configuration elements are handed to the sampler starting with the one closest to it, and the merge keeps a header that is already present, so in practice:

  • every header name from every Header Manager in scope is sent once;

  • when two managers define the same header name (compared case-insensitively), the manager closest to the sampler wins, so a sampler-level manager overrides a Thread Group-level one, exactly like a field of HTTP Request Defaults;

  • a header with an empty value in the closer manager does not remove the header: it replaces its value with an empty one, as the component reference warns.

The test-plan chapter sentence is therefore accurate for the Cookie and Authorization managers but out of date for the Header Manager. As on the scoping page, keep the layering simple (one Header Manager per scope) and confirm the headers that were really sent in the Request tab of View Results Tree, which shows the request headers, or in the jmeter.log debug output of the sampler.

A layered example for Bookshelf. The Thread Group manager sets the JSON headers and an X-Client header; the manager under "Authenticated journey" adds the bearer token and overrides X-Client:

Header Thread Group manager "Authenticated journey" manager Sent by GET /api/books

Accept

application/json

application/json

Content-Type

application/json

application/json

Authorization

Bearer ${token}

Bearer eyJ... (the thread’s token)

X-Client

bookshelf-load

bookshelf-load-auth

bookshelf-load-auth (closest wins)

Cost and pitfalls

The Header Manager is cheap: the headers are a short list resolved per request, and a merge copies that list. The practical problems are about correctness rather than load:

  • An unresolved variable is sent literally. If ${token} has not been set yet (the login failed, or the header is in scope of the login request itself), JMeter sends the text Bearer ${token}. The server answers 401, which looks like a server problem in the report. Scope the Authorization header so that the login request does not get it, give the JSON Extractor a recognisable default value, and assert on the login response.

  • Content-Type for files and forms. A Thread Group-wide Content-Type: application/json also reaches multipart uploads and form posts, where the sampler builds its own content type. Override it in a closer manager for those samplers or keep the JSON content type at the level of the samplers that send JSON.

  • Do not copy browser headers blindly. Recorded plans carry Cookie, Authorization and caching headers in Header Managers. Those values belong to the recording session; the Cookie and Cache managers and an extracted token must produce them at run time instead. The recording page later in this guide explains which headers the recorder drops.

  • Secrets in the plan. A literal token or API key in a Header Manager is saved in the .jmx file. Read it from a property (${__P(apiKey)}, set with -JapiKey=... on the command line) or from a CSV file instead.

The Cookie Manager has two functions. First, it stores and sends cookies like a browser: when a response contains a cookie, the manager stores it and sends it on later requests to the same site. Each thread has its own cookie storage area, so a site that keeps the session in a cookie gets one session per virtual user. Cookies received this way do not appear in the manager’s table; they are visible in the request and response headers in View Results Tree. Second, a plan can type cookies into the manager by hand; those cookies are shared by every thread and are created with an expiry time far in the future.

The manager checks that a received cookie is valid for the URL that set it, so cross-domain cookies are not stored. Cookies with null (empty) values are ignored, and a cookie name must be unique in the table: a second row with the same name replaces the first.

Fields

Field Meaning Required

Name

Descriptive name shown in the tree.

No

Clear cookies each iteration?

If selected, all server-defined cookies are cleared each time the main Thread Group loop starts a new iteration. Cookies defined in the GUI table are not cleared. Disabled when the next option is selected.

Yes

Use Thread Group configuration to control cookie clearing

Delegates the decision to the Thread Group’s Same user on each iteration option: cookies are cleared at the start of an iteration only when that option is unchecked. See Clearing state between iterations.

No

Cookie Policy

The cookie specification used to parse and match cookies. standard is the default since 3.0 and should work in most cases; ignoreCookies is equivalent to having no Cookie Manager.

Yes

Implementation

HC4CookieHandler (HttpClient 4.5.x API), the default since 3.0 and the only implementation left. It is IPv6-compliant.

Yes

User-Defined Cookies

Table of hard-coded cookies used by all threads, with columns Name, Value, Domain (the host name without http://; the port is ignored), Path and Secure. The reference marks it as discouraged unless you know what you are doing.

No

The table has Add, Delete, Load (adds the cookies of a saved file to the table) and Save As (saves the table, not cookies received from responses) buttons.

The cookie policies offered by HC4CookieHandler in 5.6.3 map onto the Apache HttpClient 4.5 cookie specifications:

Policy Behaviour

standard (default)

RFC 6265, relaxed parsing: the right choice for current servers.

standard-strict

RFC 6265, strict parsing of attributes such as Expires.

default, best-match, compatibility

HttpClient’s DefaultCookieSpec, which applies RFC 2965, RFC 2109 or Netscape rules depending on the cookie; kept for old plans.

netscape

The original Netscape draft specification.

rfc2109, rfc2965

The obsolete cookie RFCs.

ignoreCookies

Accepts and sends no cookies.

Source: HTTP Cookie Manager (Component Reference); the policy list is from the 5.6.3 source of HC4CookieHandler in apache/jmeter

Properties

The Cookie Manager is tuned by JMeter properties, set in user.properties or with -J on the command line:

Property Default Meaning

CookieManager.save.cookies

false

Store received cookies as thread variables.

CookieManager.name.prefix

COOKIE_

Prefix added to the cookie name to form the variable name. Set it to one or more spaces to use no prefix.

CookieManager.check.cookies

true

Check that a received cookie is valid for the URL before storing it. Set to false to accept cross-domain cookies.

CookieManager.delete_null_cookies

true

Delete cookies with null or empty values (this also removes such rows from the GUI table when it is refreshed).

CookieManager.allow_variable_cookies

true

Whether variable cookies are allowed (false reverts to the original behaviour).

cookies

cookies

Name of a Netscape-format cookie file.

With CookieManager.save.cookies=true, every cookie a thread receives is also stored as a variable named COOKIE_<name>: a cookie named SESSION is available as ${COOKIE_SESSION}. The prefix prevents a cookie from silently overwriting a variable of the plan. COOKIE_cookiename is listed among the variables JMeter defines internally. This is the simple way to echo a cookie into a header, for example a CSRF token that the server sets as a cookie and expects back in a header:

# user.properties
CookieManager.save.cookies=true
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="CSRF header">
  <collectionProp name="HeaderManager.headers">
    <elementProp name="" elementType="Header">
      <stringProp name="Header.name">X-CSRF-TOKEN</stringProp>
      <stringProp name="Header.value">${COOKIE_CSRF_TOKEN}</stringProp>
    </elementProp>
  </collectionProp>
</HeaderManager>
<hashTree/>

The standard arrangement is a single Cookie Manager directly under each Thread Group, with the standard policy:

<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager">
  <collectionProp name="CookieManager.cookies"/>
  <boolProp name="CookieManager.clearEachIteration">false</boolProp>
  <boolProp name="CookieManager.controlledByThreadGroup">true</boolProp>
  <stringProp name="CookieManager.policy">standard</stringProp>
  <stringProp name="CookieManager.implementation">org.apache.jmeter.protocol.http.control.HC4CookieHandler</stringProp>
</CookieManager>
<hashTree/>

A user-defined cookie, for example a feature flag cookie expected by every request to localhost, is a Cookie element in CookieManager.cookies:

<collectionProp name="CookieManager.cookies">
  <elementProp name="feature" elementType="Cookie" testname="feature">
    <stringProp name="Cookie.value">new-search</stringProp>
    <stringProp name="Cookie.domain">localhost</stringProp>
    <stringProp name="Cookie.path">/</stringProp>
    <boolProp name="Cookie.secure">false</boolProp>
    <longProp name="Cookie.expires">0</longProp>
    <boolProp name="Cookie.path_specified">true</boolProp>
    <boolProp name="Cookie.domain_specified">true</boolProp>
  </elementProp>
</collectionProp>

Cost, pitfalls and the 6.0 change

Memory grows with the number of threads times the cookies each thread holds; for an API that sets one or two cookies this is negligible. The points that matter are:

  • No Cookie Manager, no session. Without one, a cookie-based login works for the login request and every later request is anonymous. Most "everything after login fails" reports in a new plan come from a missing Cookie Manager.

  • Hand-typed cookies are global. A session cookie copied from a browser into the table makes every thread share one session, which is neither realistic nor usually allowed by the server.

  • More than one Cookie Manager in scope. Only one is used, there is no way to choose which, and a cookie stored in one manager is not available to another. Keep one per Thread Group.

  • Planned for 6.0: expired cookies stop being sent. The 6.0 change log (on master, not released) lists as an incompatible change that the Cookie Manager no longer sends cookies after their expiry time; in 5.6.3 and earlier they kept being sent indefinitely. A long soak test that outlives a session cookie therefore starts failing with authentication or session errors in 6.0 unless the plan logs in again or refreshes the session. Design soak tests with re-authentication now, and they will behave the same on both versions.

HTTP Cache Manager

The Cache Manager adds browser-like caching to the HTTP requests in its scope. Each virtual user thread has its own cache, which holds up to 5000 entries by default with a least-recently-used eviction policy.

How it works, according to the component reference:

  1. When a sample succeeds (a 2xx response code), the manager saves the Last-Modified and Etag values (and the expiry, when relevant) for the URL.

  2. Before the next request to that URL, the sampler looks for an entry in the cache. If there is one, it sets the conditional headers (If-Modified-Since and If-None-Match).

  3. If Use Cache-Control/Expires header when processing GET requests is selected, the stored Cache-Control / Expires value is also checked against the current time. For a GET whose entry has not expired, the sampler returns immediately without contacting the server, as a browser would.

  4. A Cache-Control: no-cache response is stored as already expired, so it always produces a conditional GET. For any other Cache-Control value, max-age gives the lifetime; without it, Expires is used; without both, the lifetime is derived from Last-Modified and the response Date as in RFC 2616 section 13.2.4.

When the document has not changed since it was cached, or its expiry is in the future, the response body is empty, which can break assertions that check the body.

Fields

Field Meaning Required

Name

Descriptive name shown in the tree.

No

Clear cache each iteration?

If selected, the cache is cleared when a new iteration of the main Thread Group loop starts. (The component reference words it as "at the start of the thread"; the 5.6.3 code clears it at each iteration start.) Disabled when the next option is selected.

Yes

Use Thread Group configuration to control cache clearing

Delegates the decision to the Thread Group’s Same user on each iteration option.

No

Use Cache-Control/Expires header when processing GET requests

Honour Cache-Control / Expires: serve fresh GET responses from the cache without sending a request. Selected by default.

Yes

Max Number of elements in cache

Maximum entries per thread (default 5000). Larger values consume more heap.

Yes

Properties

Property Default Meaning

cacheable_methods

GET

Space- or comma-separated list of HTTP methods whose responses can be cached.

cache_manager.cached_resource_mode

RETURN_NO_SAMPLE

What a cache hit produces. RETURN_NO_SAMPLE: no sample result at all (the behaviour since 2.12, in line with browsers). RETURN_200_CACHE: a sample with response code 200 and message (ex cache). RETURN_CUSTOM_STATUS: a sample with the code and message of the next two properties.

RETURN_CUSTOM_STATUS.code

empty

Response code for RETURN_CUSTOM_STATUS.

RETURN_CUSTOM_STATUS.message

empty

Response message for RETURN_CUSTOM_STATUS.

In the .jmx file:

<CacheManager guiclass="CacheManagerGui" testclass="CacheManager" testname="HTTP Cache Manager">
  <boolProp name="clearEachIteration">false</boolProp>
  <boolProp name="useExpires">true</boolProp>
  <boolProp name="CacheManager.controlledByThread">true</boolProp>
  <intProp name="maxSize">5000</intProp>
</CacheManager>
<hashTree/>

When to use it, and what it costs

For a site test with embedded resources (images, scripts, style sheets), the Cache Manager is what keeps a returning user from downloading every resource again, and leaving it out overstates the load. For a JSON API such as Bookshelf, it matters only if the API sends validators (ETag, Last-Modified) or freshness headers and real clients honour them. Many APIs send Cache-Control: no-store or nothing, and then the manager only adds work.

Worked numbers. The cache is per thread, so the worst-case number of entries is threads × max size: 50 threads × 5000 = 250,000 entries in the load generator’s heap. In practice the number of entries per thread is the number of distinct cacheable URLs a user requests. A Bookshelf user who draws from 200 search terms and views up to 300 books creates at most 200 + 300 = 500 entries, 25,000 for 50 threads, well below the limit. Raising the maximum to cache everything for a site with thousands of resources multiplies heap use by the thread count, so adjust -Xmx (the HEAP variable) accordingly, as the component reference advises.

Reporting pitfall. With the default RETURN_NO_SAMPLE, a fresh cache hit produces no sample, so the sample count and throughput in the report drop while users keep "using" the page. That is realistic for server load, but it makes the request count in the report lower than the iteration count suggests. Choose RETURN_200_CACHE if the report must show every logical request.

HTTP Authorization Manager

The Authorization Manager specifies user logins for resources protected by server authentication: the kind of protection for which a browser shows its own login dialog (HTTP Basic, Digest, or Kerberos/SPNEGO), not a login form or a token endpoint. JMeter sends the credentials when it requests a matching URL.

Fields

Field Meaning Required

Name

Descriptive name shown in the tree.

No

Clear auth on each iteration?

Used by Kerberos: if selected, authentication is performed again on each iteration of the main Thread Group loop even if it was done in a previous one. Useful when each iteration represents a different virtual user. Disabled when the next option is selected.

Yes

Use Thread Group configuration to control clearing

Delegates the decision to the Thread Group’s Same user on each iteration option.

No

Base URL

A partial or complete URL that matches one or more request URLs. With a Base URL of http://localhost:8080/admin/, a request to http://localhost:8080/admin/books/import sends that row’s credentials.

Yes

Username

The user name to authorise. Variables are allowed, so each thread can use its own account from a CSV file.

Yes

Password

The password for that user. It is stored unencrypted in the test plan.

Yes

Domain

The domain to use for NTLM.

No

Realm

The realm to use for NTLM. Applies only to the HttpClient implementation.

No

Mechanism

The type of authentication: see the next table. In the .jmx file BASIC, DIGEST or KERBEROS (the old BASIC_DIGEST value is deprecated in favour of BASIC).

No

The table, titled Authorizations Stored in the Authorization Manager, has Add, Delete, Load (adds the entries of a saved file) and Save As buttons. Saving the test plan saves every row, passwords included, unencrypted.

URL matching checks the rows in order and stops at the first match, so list the most specific Base URLs first. Duplicate URLs are ignored.

Mechanisms by implementation

HTTP Request implementation Mechanisms Pre-emptive?

HttpClient4 (default)

BASIC, DIGEST and KERBEROS

Basic authentication is pre-emptive by default since 3.2 (httpclient4.auth.preemptive=true): the Authorization header is sent on the first request and is visible in View Results Tree. Set the property to false to authenticate only in response to a 401 challenge (one extra round trip per new connection).

Java

BASIC only

Always pre-emptive, but the Authorization header is not returned when JMeter collects the request headers, so it may not appear in the View Results Tree Request tab.

Basic authentication with per-thread credentials

Suppose the Bookshelf API’s administrative import endpoints under /admin/ were protected by HTTP Basic, with one test account per thread in admins.csv (placeholder values only):

adminUser,adminPassword
admin01,change-me-01
admin02,change-me-02

Add a CSV Data Set Config (variable names adminUser,adminPassword) and an HTTP Authorization Manager to the Thread Group, with one row whose Base URL is http://${__P(host,localhost)}:${__P(port,8080)}/admin/, Username ${adminUser}, Password ${adminPassword} and Mechanism BASIC:

<AuthManager guiclass="AuthPanel" testclass="AuthManager" testname="HTTP Authorization Manager">
  <collectionProp name="AuthManager.auth_list">
    <elementProp name="" elementType="Authorization">
      <stringProp name="Authorization.url">http://${__P(host,localhost)}:${__P(port,8080)}/admin/</stringProp>
      <stringProp name="Authorization.username">${adminUser}</stringProp>
      <stringProp name="Authorization.password">${adminPassword}</stringProp>
      <stringProp name="Authorization.domain"></stringProp>
      <stringProp name="Authorization.realm"></stringProp>
      <stringProp name="Authorization.mechanism">BASIC</stringProp>
    </elementProp>
  </collectionProp>
  <boolProp name="AuthManager.controlledByThreadGroup">false</boolProp>
</AuthManager>
<hashTree/>

The plan now contains ${adminPassword}, not a password, and the CSV file can be kept out of version control. For a one-off request to a Basic-protected endpoint a Header Manager with a computed header works too, using the core __groovy function to base64-encode the pair:

Authorization: Basic ${__groovy("${adminUser}:${adminPassword}".bytes.encodeBase64().toString())}

Kerberos (SPNEGO)

Kerberos needs at least two JVM system properties, which can be put in bin/system.properties:

# bin/system.properties
java.security.krb5.conf=krb5.conf
java.security.auth.login.config=jaas.conf

The sample krb5.conf and jaas.conf files in JMeter’s bin folder point to further documentation. The related JMeter properties are:

Property Default Meaning

kerberos_jaas_application

JMeter

Name of the application module used in jaas.conf.

kerberos.spnego.strip_port

true

Strip the port from the URL before building the service principal name, like Internet Explorer and Firefox. Set to false to emulate Chrome’s --enable-auth-negotiate-port for non-standard ports.

kerberos.spnego.use_canonical_host_name

not stated

Canonicalise the host name used for the service principal name.

kerberos.spnego.delegate_cred

false

Delegate credentials in SPNEGO authentication.

Cost and pitfalls

Pre-emptive Basic adds a header and nothing else. Digest and challenge-based Basic add a 401 round trip whenever the client has no cached authentication state, which shows up as extra samples or extra latency. Kerberos adds the ticket exchange with the KDC; with Clear auth on each iteration? every iteration repeats it, which loads the KDC as well as the system under test. Other points:

  • It is not for form logins or tokens. A login form or POST /api/auth/login is a normal sampler; the session it creates lives in a cookie (Cookie Manager) or a token (extractor plus Header Manager).

  • Base URL mismatch. A Base URL with https while the requests use http, or with a host name while the requests use an IP address, never matches, and the requests go out without credentials.

  • Passwords in clear. Anything typed into the table is saved in the .jmx file and in any copy of it. Use variables.

Clearing state between iterations

A Thread Group iteration is one pass of the user journey. Whether the next pass is the same user (keeps cookies, cache and authentication) or a new one (starts clean) is a modelling decision. Since 5.2 it can be made in one place, the Thread Group’s Same user on each iteration option, which the three managers obey when their Use Thread Group configuration…​ box is selected. The reference describes the option: if selected, cookie and cache data from earlier responses are used in later iterations; if not, they are not, and a new connection is opened between iterations, which increases response times and resource use.

The 5.6.3 code applies the same rule in all three managers at the start of each iteration:

"Use Thread Group configuration" Thread Group "Same user on each iteration" Manager "Clear …​ each iteration" Result at iteration start

selected

selected

ignored (disabled)

Keep cookies, cache and Kerberos subjects.

selected

unchecked

ignored (disabled)

Clear them: every iteration is a new user.

not selected

ignored

selected

Clear them.

not selected

ignored

not selected

Keep them.

The Cookie Manager resets to its user-defined cookies; the Cache Manager empties the cache; the Authorization Manager clears its Kerberos subjects. Variables, such as an extracted token, are never cleared by these options.

For Bookshelf, where each thread logs in once and then repeats the journey, select Same user on each iteration (the default) and Use Thread Group configuration…​ on each manager. For a model where each iteration is a fresh visitor, uncheck the Thread Group option and move the login inside the iteration instead of a Once Only Controller.

Source: Thread Group (Component Reference) and the 5.6.3 sources of CookieManager, CacheManager and AuthManager in apache/jmeter

URL-rewriting sessions: the HTTP URL Re-writing Modifier

Some web applications keep the session id in the URL instead of a cookie, either as a parameter (?sid=...) or as a path extension (;jsessionid=...). JMeter then has to read the id from the previous response and add it to the next request. The HTTP URL Re-writing Modifier is a pre-processor built for this: it works like the HTML Link Parser but is simpler and more efficient for this one job. Given the name of the session parameter, it finds the value anywhere in the previous response (in a link or a form) and adds it to every request in its scope, replacing a value the request already has. Placed at Thread Group level, like a Cookie Manager, it affects every request of the thread; placed under a controller or a sampler it modifies only those requests.

Field Meaning Required

Name

Descriptive name shown in the tree.

No

Session Argument Name

The name of the parameter to grab from the previous response, wherever it appears (an href or a form).

Yes

Path Extension (use ";" as separator)

Select when the application appends the id to the path after a semicolon (;jsessionid=...) instead of a query parameter.

No

Do not use equals in path extension (Intershop Enfinity compatibility)

The application writes the extension without = between the name and the value.

No

Do not use questionmark in path extension (Intershop Enfinity compatibility)

Prevents the query string from ending up in the path extension.

No

Cache Session Id?

Save the last value found and reuse it when the previous response does not contain the session id.

Yes

URL Encode

URL-encode the value when writing the parameter.

No

<URLRewritingModifier guiclass="URLRewritingModifierGui" testclass="URLRewritingModifier" testname="jsessionid in URL">
  <stringProp name="argument_name">jsessionid</stringProp>
  <boolProp name="path_extension">true</boolProp>
  <boolProp name="path_extension_no_equals">false</boolProp>
  <boolProp name="path_extension_no_questionmark">false</boolProp>
  <boolProp name="cache_value">true</boolProp>
  <boolProp name="encode">false</boolProp>
</URLRewritingModifier>
<hashTree/>

Distributed testing pitfall. The modifier reads the previous response. The component reference warns that in distributed testing the sample sender mode (the mode property in jmeter.properties) must not be a stripping one, such as the default StrippedBatch, which removes the response data (Bug 56376).

The Bookshelf API uses tokens, not URL rewriting, so this element is not in bookshelf-load.jmx; it is useful for older servlet or portal applications.

Why only one manager applies per sampler

The managers are configuration elements and follow the hierarchical scoping rules: a sampler collects every configuration element on the path from itself up to the Test Plan. Defaults elements (HTTP Request Defaults) are merged field by field, and so, in 5.6.3, are Header Managers. The Cookie, Cache and Authorization managers are different: a sampler can hold only one of each. When several are in scope, the 5.6.3 sampler replaces the one it has with the next one it is given and writes a warning such as Existing CookieManager ... superseded by ... to jmeter.log. Because the elements are handed over starting with the closest one, the one that survives is not the closest, which is the opposite of what most people expect, and the documentation says only that there is "no way to specify which one is used". Two practical consequences:

  • State is split. Cookies stored by one Cookie Manager are not visible to another, so a login handled by a controller-level manager and later requests handled by the Thread Group manager lose the session.

  • Overriding does not work. A Cookie or Authorization manager under one sampler cannot reliably override the Thread Group’s one, the way a Header Manager or an HTTP Request Defaults can.

The rule that follows is the one the user manual and component reference suggest: one Cookie Manager, one Cache Manager and at most one Authorization Manager per Thread Group, with all URLs the Authorization Manager must cover in its single table. If the jmeter.log of a run contains superseded by, the plan has more than one in scope.

flowchart TD TP["Test Plan"] --> TG["Thread Group"] TG --> H1["Header Manager A: Accept, Content-Type"] TG --> C1["Cookie Manager 1"] TG --> SC["Simple Controller"] SC --> H2["Header Manager B: Authorization"] SC --> C2["Cookie Manager 2"] SC --> R["GET /api/loans/id"] R -.->|"headers: A and B merged, B wins on equal names"| OUT1["sent headers"] R -.->|"cookies: only one manager used, the other is superseded"| OUT2["one cookie store"]

Worked example: a JWT bearer flow for Bookshelf

The Bookshelf API authenticates with a JSON Web Token: POST /api/auth/login with a JSON body containing a user name and password returns a JSON document with the token, and every other endpoint expects it in an Authorization: Bearer <token> header. This is the common shape of a token-protected API: the JWT and the JOSE Family page describes the token itself, and Access and Refresh Tokens describes lifetimes and refresh. A real OAuth 2.0 authorization server would issue the token from its token endpoint instead (for a machine client, the client credentials grant); the JMeter side is the same: one request obtains the token, an extractor stores it, a Header Manager sends it.

The response of the login endpoint in this example:

{ "token": "eyJhbGciOiJIUzI1NiJ9.placeholder.signature", "expiresIn": 900 }
sequenceDiagram participant T as JMeter thread participant API as Bookshelf API Note over T: Once Only Controller, first iteration only T->>API: POST /api/auth/login {username, password from users.csv} API-->>T: 200 {"token": "eyJ...", "expiresIn": 900} Note over T: JSON Extractor stores $.token in variable token loop every iteration T->>API: GET /api/books?query=term (Authorization: Bearer token) API-->>T: 200 results T->>API: POST /api/loans (Authorization: Bearer token) API-->>T: 201 loan end

Steps in the GUI

  1. Credentials. Create users.csv next to the plan, one placeholder test account per line:

    username,password
    reader01,change-me-01
    reader02,change-me-02
    reader03,change-me-03

    Add Add → Config Element → CSV Data Set Config to the Thread Group: Filename users.csv, Variable Names left empty (the first line is the header), Recycle on EOF True, Sharing mode All threads. Each thread reads one line on each iteration; because the login runs only once per thread, each thread keeps the user of its first line. The parameterisation page in this group covers the CSV options in detail.

  2. JSON headers for everyone. Add Add → Config Element → HTTP Header Manager to the Thread Group with Accept: application/json and Content-Type: application/json.

  3. Cookie Manager. Add Add → Config Element → HTTP Cookie Manager to the Thread Group, policy standard, Use Thread Group configuration to control cookie clearing selected. Bookshelf does not need cookies, but a gateway or load balancer in front of it may set a sticky-session cookie; a real client would return it.

  4. Login once. Add Add → Logic Controller → Once Only Controller named "Login once per user" and, inside it, an HTTP Request POST /api/auth/login with Body Data:

    {"username":"${username}","password":"${password}"}
  5. Extract the token. Right-click the login request, Add → Post Processors → JSON Extractor: Names of created variables token, JSON Path expressions $.token, Match No. 1, Default Values TOKEN_NOT_FOUND. Add Add → Assertions → Response Assertion checking that the response code equals 200, so a failed login is reported as the login’s failure and not as 401 errors further on.

  6. Send the token. Add Add → Logic Controller → Simple Controller named "Authenticated journey" after the Once Only Controller. Inside it, add an HTTP Header Manager with Authorization: Bearer ${token}, then the journey samplers. Because this Header Manager is not in scope of the login request, the login never sends the placeholder text.

The plan fragment

<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Borrowers">
  <stringProp name="ThreadGroup.num_threads">${__P(threads,1)}</stringProp>
  <stringProp name="ThreadGroup.ramp_time">${__P(rampup,1)}</stringProp>
  <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
  <boolProp name="ThreadGroup.scheduler">true</boolProp>
  <stringProp name="ThreadGroup.duration">${__P(duration,60)}</stringProp>
  <stringProp name="ThreadGroup.delay"></stringProp>
  <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
  <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController">
    <intProp name="LoopController.loops">-1</intProp>
    <boolProp name="LoopController.continue_forever">false</boolProp>
  </elementProp>
</ThreadGroup>
<hashTree>
  <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="JSON headers">
    <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/>
  <CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager">
    <collectionProp name="CookieManager.cookies"/>
    <boolProp name="CookieManager.clearEachIteration">false</boolProp>
    <boolProp name="CookieManager.controlledByThreadGroup">true</boolProp>
    <stringProp name="CookieManager.policy">standard</stringProp>
  </CookieManager>
  <hashTree/>
  <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="users.csv">
    <stringProp name="filename">users.csv</stringProp>
    <stringProp name="fileEncoding">UTF-8</stringProp>
    <stringProp name="variableNames"></stringProp>
    <boolProp name="ignoreFirstLine">false</boolProp>
    <stringProp name="delimiter">,</stringProp>
    <boolProp name="quotedData">false</boolProp>
    <boolProp name="recycle">true</boolProp>
    <boolProp name="stopThread">false</boolProp>
    <stringProp name="shareMode">shareMode.all</stringProp>
  </CSVDataSet>
  <hashTree/>
  <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Login once per user"/>
  <hashTree>
    <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /api/auth/login">
      <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>
      <stringProp name="HTTPSampler.path">/api/auth/login</stringProp>
      <stringProp name="HTTPSampler.method">POST</stringProp>
    </HTTPSamplerProxy>
    <hashTree>
      <JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Extract token">
        <stringProp name="JSONPostProcessor.referenceNames">token</stringProp>
        <stringProp name="JSONPostProcessor.jsonPathExprs">$.token</stringProp>
        <stringProp name="JSONPostProcessor.match_numbers">1</stringProp>
        <stringProp name="JSONPostProcessor.defaultValues">TOKEN_NOT_FOUND</stringProp>
      </JSONPostProcessor>
      <hashTree/>
      <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Login returns 200">
        <collectionProp name="Asserion.test_strings">
          <stringProp name="49586">200</stringProp>
        </collectionProp>
        <stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
        <intProp name="Assertion.test_type">8</intProp>
      </ResponseAssertion>
      <hashTree/>
    </hashTree>
  </hashTree>
  <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Authenticated journey"/>
  <hashTree>
    <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="Bearer token">
      <collectionProp name="HeaderManager.headers">
        <elementProp name="" elementType="Header">
          <stringProp name="Header.name">Authorization</stringProp>
          <stringProp name="Header.value">Bearer ${token}</stringProp>
        </elementProp>
      </collectionProp>
    </HeaderManager>
    <hashTree/>
    <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /api/books">
      <stringProp name="HTTPSampler.path">/api/books?query=jmeter</stringProp>
      <stringProp name="HTTPSampler.method">GET</stringProp>
    </HTTPSamplerProxy>
    <hashTree/>
  </hashTree>
</hashTree>

Host and port come from the HTTP Request Defaults at Test Plan level, as on the other pages of this section, and the remaining journey requests (view a book, create and view a loan) follow the same pattern as GET /api/books. The Asserion.test_strings spelling is JMeter’s own property name, not a typo in this page.

Source: Once Only Controller, JSON Extractor and HTTP Header Manager (Component Reference)

Running it

A smoke run with one user, then the standard load profile (50 threads, 60 s ramp-up, 660 s duration, which leaves a 10-minute hold):

jmeter -n -t bookshelf-load.jmx -l smoke.jtl -Jthreads=1 -Jrampup=1 -Jduration=30
jmeter -n -t bookshelf-load.jmx -l load.jtl -Jthreads=50 -Jrampup=60 -Jduration=660

The summariser lines of the load run look like this (illustrative values; a local run will differ):

summary +    135 in 00:00:28 =    4.8/s Avg:    41 Min:     7 Max:   288 Err:     0 (0.00%) Active: 24 Started: 24 Finished: 0
summary +    498 in 00:00:30 =   16.6/s Avg:    44 Min:     6 Max:   412 Err:     0 (0.00%) Active: 50 Started: 50 Finished: 0

Err: 0 and no 401 responses are the signal that the token is extracted and sent. If every request after the login fails with 401, look at the Request tab of View Results Tree in a GUI smoke run: an Authorization header with the value Bearer TOKEN_NOT_FOUND means the JSON path did not match; a header with the literal text Bearer ${token} means the Header Manager was evaluated before any extractor set the variable.

Worked numbers: login once versus login every iteration

Assume one journey iteration, including think time, takes 12 s per user. During the 10-minute hold each of the 50 threads runs 600 / 12 = 50 iterations, 2,500 in total.

  • Login once per user (Once Only Controller): 50 login requests in the whole test, all during the 60 s ramp-up (about 50 / 60 ≈ 0.8 logins/s), and 0 during the hold.

  • Login on every iteration: 2,500 logins during the hold, 2,500 / 600 ≈ 4.2 logins/s on top of the other traffic. Password hashing makes a login one of the most expensive requests a service has, so this second plan measures a different system.

Neither is right in general: the workload model decides. The Bookshelf mix includes 10 % login-heavy iterations (short sessions that authenticate every time), which add 0.10 × 2,500 = 250 logins over the hold, about 0.4/s, on top of the 50 initial ones.

Token expiry in long tests

The token in this example lives 900 s (15 minutes). The 660 s load test ends before it expires, so logging in once is enough. A soak test of several hours is not: after 15 minutes every request fails with 401. Replace the Once Only Controller with an If Controller that logs in when there is no token yet or the token is older than a safety margin, and record the login time after the extraction:

// JSR223 PostProcessor under POST /api/auth/login, after the JSON Extractor
vars.put('tokenTime', String.valueOf(System.currentTimeMillis()))

If Controller condition, with Interpret Condition as Variable Expression? selected (re-login after 12 of the 15 minutes):

${__groovy(vars.get('tokenTime') == null || System.currentTimeMillis() - Long.parseLong(vars.get('tokenTime')) > 720000)}

If the server issues refresh tokens, the If Controller can call the refresh endpoint instead of logging in again; how refresh tokens work is described in Access and Refresh Tokens, and token-related failure modes in Testing and Debugging OAuth. The same design also covers the planned 6.0 Cookie Manager change for cookie-based sessions.

Keeping tokens out of results

Request headers are not written to result files by default (jmeter.save.saveservice.requestHeaders=false), so the tokens do not end up in a .jtl file. View Results Tree shows them in full, which is one more reason to use it only for debugging small runs. Test accounts and tokens should belong to a test environment; credentials come from CSV files or -J properties with placeholder values in the plan.

Load-generator cost at a glance

Element Cost on the load generator

HTTP Header Manager

A few strings per request; negligible. Many managers in scope mean a list merge per request.

HTTP Cookie Manager

Cookie parsing and matching per request; memory = threads × cookies per thread, usually small.

HTTP Cache Manager

Up to maxSize entries per thread (default 5000); the largest memory consumer of the four on a site with many resources. Saves network time when hits are served locally.

HTTP Authorization Manager

Negligible for pre-emptive Basic; an extra round trip for challenge-based schemes; Kerberos ticket exchanges, repeated per iteration when clearing is on.

HTTP URL Re-writing Modifier

Scans the previous response body for the session parameter on every request in scope.

Pitfalls

  • Missing Cookie Manager: a cookie-based session is lost after the login request.

  • More than one Cookie, Cache or Authorization manager in scope: one is silently superseded; look for superseded by in jmeter.log.

  • Assuming the closest Cookie Manager wins: it does not; only Header Managers (and Defaults) merge with the closest value winning.

  • Authorization header in scope of the login: the literal Bearer ${token} is sent; scope the Header Manager to the authenticated requests.

  • Hand-typed cookies or tokens from a browser: shared by every thread, expire mid-test, and leak into the .jmx.

  • Cache Manager on a JSON API: empty 304 bodies break body assertions, and cache hits produce no sample by default.

  • Same user on each iteration unchecked: with Use Thread Group configuration…​ selected on the managers, every iteration loses cookies and cache while variables (such as a token) survive, a mix that matches no real user.

  • Passwords in the Authorization Manager: stored unencrypted; use variables.

  • Distributed runs with URL rewriting: a stripping sample sender mode breaks the modifier.

From the books to 5.6 to 6.0

Topic Books (JMeter 2.11 to 3.0) 5.6.3 and planned 6.0

Cookie policy and implementation

The books target versions in which the HttpClient 3.1 implementation was still available (it was dropped in 3.2).

standard policy and HC4CookieHandler are the defaults since 3.0; HC4CookieHandler is the only implementation left.

Clearing per iteration

Only the per-manager "Clear …​ each iteration" boxes.

Since 5.2 the Thread Group’s Same user on each iteration can drive the three managers.

Pre-emptive Basic authentication

Challenge-based by default with HttpClient.

Pre-emptive by default since 3.2 (httpclient4.auth.preemptive).

Several Header Managers

Multiple Header Managers per sampler have been supported since 2.3.3.

Merged (5.6.3 source), closest wins on equal names; the test-plan chapter still says otherwise.

Expired cookies

Sent indefinitely.

Still sent in 5.6.3; planned for 6.0: no longer sent after expiry (incompatible change).

Token correlation

JSON PathExtractor plugin or regular expressions.

Core JSON Extractor (and JMESPath, Boundary extractors).

Session handling with cookies and URL rewriting is treated as a methodology topic in Performance Testing with JMeter, 2nd ed., chapter 4 (Managing Sessions); the element names, fields and defaults on this page come from the 5.6.3 documentation and sources instead.

References