Chapter 09Lesson 01~135 minutes

Pre-Processors, Post-Processors, Extractors, and Correlation: Core Concepts and Mental Model

Chapter 08 proved that responses are functionally correct. Chapter 09 makes multi-step requests causally correct: values emitted by one response are extracted after that sample, stored in that virtual user's variable state, and used to build the next request instead of replaying one token captured during recording.

CorrelationPost-ProcessorsPre-ProcessorsExtractorsThread-local state

Learning objectives

  • Place Pre-Processors and Post-Processors from execution semantics rather than visual ordering.
  • Explain correlation as response-derived, thread-local state flowing into a later request.
  • Distinguish a JMeter variable from a shared JMeter property for per-user session data.
  • Choose a dedicated extractor before reaching for JSR223 code.
  • Define an explicit missing-extraction sentinel and correctness check.
  • Inspect correlation without leaking real credentials or session identifiers.

1. Why recorded tokens make invalid multi-user scripts

A browser recording often captures a concrete CSRF token, order ID, request ID, or session handle. Replaying that literal value may work for one request and fail as soon as the server rotates state or several users execute concurrently. Worse, a test can send the same captured session identity from every JMeter thread and accidentally benchmark one shared server-side session.

Mandatory lab boundary: all examples use only synthetic tokens from http://127.0.0.1:8000, at most 3 threads, and at most 3 loops. Never print, commit, or copy real bearer tokens, CSRF values, session cookies, authorization headers, or customer identifiers into course artifacts.

2. Mental model: correlation follows causality

A response-derived value becomes the next request's per-user state

Follow the arrows as a causal data flow. The response exists before the extractor can create a variable, and the downstream request is constructed from that thread's own variable state.

flowchart TD
A[Sampler A: start session] --> R[Response contains synthetic token]
R --> X[Post-Processor / extractor]
X --> V[Thread-local CORR_TOKEN]
V --> P[Pre-Processor prepares PREPARED_TOKEN]
P --> B[Sampler B: use session]
B --> T[Authorized local target]
T --> RR[Response / status]
RR --> Q[Assertion verifies accepted]
V -. thread 1 copy .-> U1[Virtual user 1]
V -. separate thread 2 copy .-> U2[Virtual user 2]

Sampler A must complete before its Post-Processor can read the response. The extractor writes CORR_TOKEN into the current thread's JMeter variable map. Immediately before Sampler B, a Pre-Processor reads that same thread's token and derives PREPARED_TOKEN. Sampler B sends both values. An assertion then verifies the business outcome. Thread 1 and thread 2 have separate variable maps, so their correlated values do not overwrite one another.

3. Execution order: scope first, type order second

For one applicable sampler, JMeter's current execution order is: Configuration elements → Pre-Processors → Timers → Sampler → Post-Processors → Assertions → Listeners. A Post-Processor therefore cannot inspect assertion results for the same sample because assertions have not run yet.

Visual sibling order does not turn a Post-Processor into “after the sampler above it.” If a processor is scoped broadly under a controller, it can run for every sampler in that scope. Attach it as a child of the specific sampler when correlation belongs only to that response/request.

4. Pre-Processor versus Post-Processor

Element Runs Typical correlation role State touched
Pre-Processor Immediately before an in-scope sampler. Prepare/transform variables or request data required by the upcoming sample. Current thread variables and/or current sampler configuration.
Post-Processor Immediately after an in-scope sampler, before assertions. Extract response-derived IDs/tokens into variables for future requests. Previous/current SampleResult and current thread variables.
Assertion After Post-Processors. Reject missing/incorrect extraction outcome or downstream business result. Sample success/failure evidence.

5. Correlation values belong in variables, not global properties

Chapter 06 established that JMeter variables are normally thread-local while JMeter properties are shared by the entire JMeter process. A response-derived user token is therefore a variable. Writing it to props or __setProperty creates a cross-thread race and can make users reuse one another's session state.

6. Dedicated extractors should be the default

Current JMeter includes built-in Regular Expression, CSS Selector, XPath/XPath2, JSON Extractor, JSON JMESPath Extractor, and Boundary Extractor Post-Processors. Use the extractor that matches the response format. Dedicated components are easier to review and generally cheaper than arbitrary script code.

This chapter uses JSON JMESPath Extractor with expression session.token. Chapter 10 goes deeper into regex, CSS/JQuery, XPath, JSONPath, and boundary extraction choices.

7. JSON JMESPath Extractor contract

A JSON JMESPath Extractor runs after the sampler in its scope and can store a selected result in a named JMeter variable. Configure:

  • Apply to: Main sample only
  • Name of created variable: CORR_TOKEN
  • JMESPath: session.token
  • Match No.: 1
  • Default Value: CORR_MISSING

The explicit sentinel is intentional. If the response changes shape, CORR_TOKEN becomes CORR_MISSING rather than silently keeping some stale token.

8. Missing correlation must be observable

Do not let a failed extraction fall through as an unexplained literal ${CORR_TOKEN} or a previous value. During authoring, set a clear default and assert that the extracted variable is not the sentinel. In load mode, preserve the failed sample/JTL and stop or skip the remaining dependent steps according to an explicit error policy.

9. JSR223 PreProcessor is for transformation, not basic extraction

A Pre-Processor is appropriate when the next request requires a derived form that a normal JMeter field/function cannot express clearly. The mandatory lab converts the synthetic token to uppercase only to prove the processor ran before the next sampler:

def token = vars.get('CORR_TOKEN')
if (token == null || token == 'CORR_MISSING' || token.trim().isEmpty()) {
    vars.put('PREPARED_TOKEN', 'PREP_MISSING')
    vars.put('CORRELATION_STATE', 'missing')
} else {
    // Synthetic transformation used only to prove the PreProcessor runs before /session/use.
    vars.put('PREPARED_TOKEN', token.toUpperCase(Locale.ROOT))
    vars.put('CORRELATION_STATE', 'ready')
}

Use Groovy and enable compiled-script caching when scripting is needed. Do not insert ${CORR_TOKEN} directly into cached script text; use vars.get('CORR_TOKEN') so runtime per-thread values are read on every invocation.

10. Body versus header versus cookie correlation

If a token is in JSON, use JSON/JMESPath. If it is in a simple response header, a header-scoped Regex/Boundary extractor may be appropriate. If state is a normal HTTP cookie, Cookie Manager often models it directly and you should not manually extract/reinject it unless the application contract requires special handling.

11. Read-only inspection before changing correlation

  • Inspect the successful response and identify the exact dynamic field and owning sampler.
  • Search the JMX for hard-coded captured tokens/IDs and note every downstream reference.
  • List each Pre/Post-Processor and its tree parent/scope.
  • Check extractor reference name, expression, match number, and default.
  • Use a one-thread Debug Sampler to inspect only synthetic correlation variables.
  • Compare downstream request metadata and target event logs without persisting real secrets.
  • Record JTL, jmeter.log, generator headroom, and target behavior before changing load.

12. DevOps connection: correlation is reproducibility

A load plan should work when started later by another developer or CI agent because it discovers dynamic state from the system under test. Hard-coded replay data makes a plan dependent on one historical session. Proper correlation turns the JMX into a repeatable protocol workflow.

Knowledge check

Why should a response-derived session token be a JMeter variable rather than a property?

When does a Post-Processor run relative to assertions?

Why use CORR_MISSING as the extractor default during development?

Why is JSON JMESPath Extractor preferred over Groovy for session.token?

What is wrong with replaying one recorded token from every thread?

Next lesson

Build and prove a two-thread correlation flow

Lesson 2 starts a local session service, extracts one synthetic token per user, derives request metadata in a Pre-Processor, verifies variables with Debug Sampler, and proves the target receives independent token fingerprints from concurrent threads.

Official references and version notes

  • Elements of a Test Plan — Pre-Processor/Post-Processor purpose, scope, and execution order: configuration → pre-processors → timers → sampler → post-processors → assertions → listeners.
  • Component Reference — Regular Expression, JSON/JMESPath, Boundary, JSR223 Pre/Post Processor, User Parameters, and Result Status Action Handler semantics.
  • Functions and Variables — thread-local JMeter variables versus process-wide JMeter properties.
  • Best Practices — CLI load execution and scripting/performance guidance.
  • Apache JMeter downloads — current stable release and Java requirement.
Version and compatibility note

Version-sensitive behavior was rechecked against current Apache JMeter primary documentation on 2026-09-05. The course baseline remains Apache JMeter 5.6.3 with a Java 17 JDK for labs and no third-party plugins; JMeter 5.6.3 requires Java 8+. Pre-Processors execute before their in-scope sampler and Post-Processors execute after the sampler but before Assertions. Processor behavior is scope-driven rather than determined by visual sibling order. Built-in Post-Processor extractors store results in JMeter variables, which are normally thread-local. JSON JMESPath Extractor accepts one JMESPath expression, can select a match, and can set an explicit default when nothing matches. Regular Expression and Boundary Extractors likewise support explicit defaults, which are especially useful during debugging so a missing extraction is distinguishable from a processor that never ran. JSR223 Pre/Post Processors provide vars (thread variables), props (shared JMeter properties), and the relevant sampler/result context; Groovy with compiled-script caching is preferred over BeanShell when scripting is actually necessary. Mandatory labs use a built-in extractor for correlation and only a tiny Groovy PreProcessor for a deliberately simple transformation; Chapter 10 covers extractor families in greater depth.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.