skip to content

Bound Context Variables

The objects JMeter drops into a script before it runs, and the scoping that follows: one set is private to the thread, another is shared by the whole JVM, so the two behave differently under load.

on this pageshow

explore

questions

6

In a JMeter JSR223 PostProcessor, which bound object hands a captured value to the next sampler?

level: juniorimportance: must knowfreq 72%

answer

  1. Scripts hand values on, not back
  2. Look for the per-thread binding
  3. JMeterVariables, not the properties object
  4. One call takes a String key and value

basics

~10 s

The vars binding, which is the running thread's JMeterVariables object. Call vars.put("orderId", id) inside the script and any later element in that same thread reads the value as ${orderId}.

solid answer

~40 s

JMeter fills the script engine's bindings before the script runs, and `vars` is the thread's own `JMeterVariables` instance — the same object `ctx.getVariables()` returns. Handing a value forward is a **write**, not a return value: `vars.put("orderId", id)` stores it, and the next sampler picks it up through a normal `${orderId}` reference in any of its fields. The value the script itself evaluates to is thrown away by a JSR223 PostProcessor, and a plain Groovy local variable does not survive either, because each invocation is evaluated against a freshly created `Bindings`. Use `props` instead only when the value must outlive the thread, since `props` is the one JVM-wide `java.util.Properties` object.

code

groovy · 7 lines
groovy
// JSR223 PostProcessor under the "POST /orders" sampler (Language: groovy)
String id = prev.getResponseDataAsString().trim()   // body is the new order id

vars.put("orderId", id)                            // per-thread, String value
log.info("captured order {} on thread {}", id, ctx.getThreadNum())

// The next HTTP Request's Path field:  /orders/${orderId}

go deeper

for a junior

Recall the name: vars. Know that you write with vars.put and read the value back in a later element as a normal ${name} reference, and that the script's return value is not the channel.

for a middle

Explain that vars is the thread's JMeterVariables instance, the same object ctx.getVariables() returns, and that a fresh Bindings is created for every invocation, which is why Groovy locals do not persist.

for a senior

Show how you diagnose the failure: a literal ${orderId} arriving in a URL or a log line means the write never happened, and a Debug Sampler or a log line tells you which of the two ends is wrong.

for a principal

Own the convention. Decide which values travel in vars, which are allowed into the JVM-wide props, and how variable names are namespaced so two fragments in one plan cannot collide on a key.

A JSR223 element in Apache JMeter 6.0.0 does not return data to the test plan. It hands data on by writing into an object JMeter put into the script's bindings before the script ran. ## What JMeter binds before the script runs `JSR223TestElement.populateBindings` fills the engine's `Bindings` on every invocation: - `vars` — the running thread's `JMeterVariables`; identical to `ctx.getVariables()` - `props` — the single JVM-wide `java.util.Properties` from `JMeterUtils.getJMeterProperties()` - `ctx` — that thread's `JMeterContext` - `prev` — the last `SampleResult`, and `sampler` — the current `Sampler` - `log`, `Label`, `FileName`, `Parameters`, `args`, `OUT` `vars` is the one that travels. Every element that runs later in the same thread — samplers, extractors, assertions, other scripts — reads the same `JMeterVariables` instance. ## Writing the value ```groovy vars.put("orderId", id) // put(String key, String value) ``` The next sampler needs no script at all: type `/orders/${orderId}` into its Path field. A `${name}` reference compiles to a `SimpleVariable`, whose `toString()` calls `vars.get(name)`, so the two ends meet in the same map. ## Three ways people try to do it that do not work 1. **Returning the value.** `JSR223PostProcessor.process()` calls `processFileOrScript(...)` and ignores what comes back. (The JSR223 **Sampler** is the exception: it uses the script's return value as response data when the script left the response empty.) 2. **A Groovy local.** `processFileOrScript` is called with a `null` `Bindings`, so the element asks the engine for `createBindings()` on every call. Nothing a previous invocation defined is still there. 3. **`props` by reflex.** `props.put("orderId", id)` does store something, but in the JVM-wide properties object shared by every thread in the engine, and `${orderId}` will not read it — properties are reached with the `__P` function, not with a bare variable reference. ## What the next sampler actually sees If nothing wrote the variable, the reference does **not** arrive blank. `SimpleVariable.toString()` returns the literal text `"${" + name + "}"` when `vars.get(name)` is `null`, so the request goes out with `/orders/${orderId}` in the path. That literal in a URL, a log line or an error body is the signature of a script that did not run, ran too late, or wrote a different key. ## A second channel: the sampler binding `sampler` is the `Sampler` that is about to run when the element is a **PreProcessor**, because `JMeterThread` calls `setCurrentSampler(current)` before it runs them. Cast it and change the request itself rather than going through a field: ```groovy sampler.addArgument("orderId", id) // HTTPSamplerBase sampler.setPath("/orders/" + id) ``` Reach for that only when the change cannot be expressed in a field. A `${orderId}` in the Path column is visible to anyone who opens the tree; a `setPath` call is visible only to someone who opens the script. ## Scope, in one line each | Binding | Backing object | Who sees a write | |---|---|---| | `vars` | `JMeterVariables` (per `JMeterContext`) | only the thread that wrote it | | `props` | `java.util.Properties` (static in `JMeterUtils`) | every thread in that JVM | `JMeterContextService` holds the contexts in a `ThreadLocal`, so each thread gets its own `JMeterVariables` and two threads writing `orderId` never see each other's value. That is exactly what you want for a per-user id, and exactly why `props` is the wrong reflex for one. ## Checking it Drop a **Debug Sampler** (JMeter Variables = true) after the script and read the values in a **View Results Tree**, or a **Debug PostProcessor** to get the same dump as a subsample. A one-line `log.info("orderId={}", vars.get("orderId"))` in the script itself is the cheapest check of all, and it lands in `jmeter.log` because the root logger ships at `info`.

  • A Groovy variable defined in one JMeter JSR223 element is gone by the next invocation. What causes that?
    The element calls `processFileOrScript` with a null `Bindings`, so JMeter asks the engine for `createBindings()` each time. Nothing the previous run defined is carried over. Anything that must survive goes into `vars` (per thread) or `props` (per JVM).
  • How would you confirm in JMeter that vars.put actually stored the key you expect?
    Add a **Debug Sampler** with JMeter Variables enabled and read its output in a **View Results Tree**, or a **Debug PostProcessor** for the same dump as a subsample. In a non-GUI run, `log.info` from the script into `jmeter.log` is the equivalent check.

vars is the note in your own pocket: every thread carries its own copy, so what one runner writes there is invisible to the rest. props is the noticeboard on the wall of the same building - one board, and everyone reads and writes that one.

saying these in an interview costs you the question

  • Thinks the script's return value becomes the variable
  • Reaches for props, so every thread overwrites one key
  • Expects a Groovy local to survive to the next invocation
  • Believes an unset ${orderId} arrives as an empty string
  • Cannot name the vars binding or its JMeterVariables type
open as a page

In a JMeter JSR223 PreProcessor, which sample result does the bound prev object hold?

level: middleimportance: should knowfreq 45%

basics

~20 s

The result of the previous sampler, not the one about to run. JMeter only calls setPreviousResult after a sample completes, so in a PreProcessor prev is one sampler behind, and it is null before the thread's first sample.

open as a page

In a JMeter JSR223 script, how do vars.put and vars.putObject differ?

level: middleimportance: should knowfreq 52%

basics

~20 s

vars.put is declared put(String, String) and stores a String. vars.putObject takes any Object. Both write into the same per-thread map, and vars.getObject is the only accessor that gives the stored object back with its type.

open as a page

Your JMeter JSR223 script calls log.debug and nothing reaches jmeter.log. Why?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The shipped bin/log4j2.xml sets the root logger to info, so debug events are dropped. The log binding is a real SLF4J Logger named after the element's class plus its tree name; raise that name's level, or use log.info.

open as a page

Your team's JMeter scripts reach through ctx into the thread and engine objects. Where do you draw the line?

level: principalimportance: should knowfreq 28%

basics

~20 s

Tier the surface. JMeterContext's read-only accessors are fair game; the thread, thread group and engine handles, and every method the source marks as called internally by JMeter, should need a review before a plan depends on them.

open as a page

Which objects does JMeter bind for a script named by the jsr223.init.file property?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Only three: log, props and OUT. JMeter builds that script's bindings by hand in its own startup path and never touches a JMeterContext, so there is no vars, ctx, prev, sampler, Label, FileName, Parameters or args at all.

open as a page