Skip to content

JMeter JSR223 Groovy Scripting Guide & Cookbook

Master JMeter JSR223 Groovy scripting: implicit objects (vars, props, prev, ctx), JSON slurping, HMAC hashing, caching, and load-tested recipes.

Difficulty
intermediate
Guide type
how-to
Estimated read time
14 min read
Last verified version
Verified JMeter 5.6

JMeter JSR223 Groovy Scripting Guide & Cookbook

Section titled “JMeter JSR223 Groovy Scripting Guide & Cookbook”

Apache JMeter provides JSR223 test elements (Samplers, PreProcessors, PostProcessors, Assertions, and Listeners) to execute custom logic during a test run. Apache Groovy is the standard scripting engine in JMeter: it compiles directly to Java bytecode, supports automatic script caching, and executes with near-native Java performance.

This guide provides a comprehensive reference to implicit script objects, compilation caching rules, and production-tested Groovy recipes for real-world load testing.

Every JSR223 test element automatically exposes several pre-configured objects in the script scope:

ObjectClass / TypeDescription & Primary Use Case
varsorg.apache.jmeter.threads.JMeterVariablesThread-local variable store. Read and write variables for the current virtual user.
propsjava.util.PropertiesGlobal properties shared across all threads and thread groups in the JMeter JVM instance.
prevorg.apache.jmeter.samplers.SampleResultResult of the previous sampler (PostProcessors, Assertions, Listeners).
samplerorg.apache.jmeter.samplers.SamplerThe current sampler object being executed or prepared (PreProcessors, Samplers).
ctxorg.apache.jmeter.threads.JMeterContextInternal engine context: thread number, engine state, active thread count, and current sampler.
logorg.slf4j.LoggerSLF4J logger instance writing to jmeter.log (e.g., log.info("msg"), log.error("err")).
OUTjava.io.PrintStreamStandard system output (System.out).
argsString[]Array of script parameter tokens passed in the Parameters field.

vars holds variables specific to the executing thread. Variables created here cannot be seen by other virtual users.

// Read variable
String token = vars.get("authToken")
String defaultId = vars.get("userId") ?: "guest-1000"
// Write variable
vars.put("orderId", "ORD-98765")
vars.put("isEligible", "true")
// Store non-string objects (accessible only within the same thread)
List<String> items = ["item_A", "item_B", "item_C"]
vars.putObject("userCart", items)
// Retrieve stored object
List<String> cart = (List<String>) vars.getObject("userCart")

props is a thread-safe java.util.Properties instance visible across all threads, Thread Groups, and setUp/tearDown Thread Groups.

// Read property with a fallback default
String apiHost = props.getProperty("app.host", "api.example.com")
// Write a property across thread groups
props.put("sharedAuthKey", "key-live-99212")
// Synchronized counter / token rotation
synchronized (props) {
int currentCounter = Integer.parseInt(props.getProperty("globalCounter", "0"))
props.put("globalCounter", String.valueOf(currentCounter + 1))
}

Available in JSR223 PostProcessor, JSR223 Assertion, and JSR223 Listener to inspect or modify response metadata:

// Read response status and body
int responseCode = prev.getResponseCode().toInteger()
String responseBody = prev.getResponseDataAsString()
long latencyMs = prev.getLatency()
long responseTime = prev.getTime()
// Modify sample result dynamically
if (responseBody.contains("SESSION_EXPIRED")) {
prev.setSuccessful(false)
prev.setResponseCode("401")
prev.setResponseMessage("Session expired detected in payload")
}
// Ignore sampler from final dashboard / metrics if needed
if (prev.getSampleLabel().startsWith("HealthCheck")) {
prev.setIgnore()
}

Recipe A: Parsing & Extracting JSON with JsonSlurper

Section titled “Recipe A: Parsing & Extracting JSON with JsonSlurper”
import groovy.json.JsonSlurper
String response = prev.getResponseDataAsString()
def json = new JsonSlurper().parseText(response)
// Extract top-level or nested values
String jwtToken = json.auth.access_token
int totalOrders = json.data.orders.size()
// Find items matching condition
def activeOrder = json.data.orders.find { it.status == "ACTIVE" }
if (activeOrder) {
vars.put("activeOrderId", activeOrder.id.toString())
}
vars.put("jwtToken", jwtToken)
vars.put("totalOrders", totalOrders.toString())

Recipe B: Generating JSON Request Payloads with JsonOutput

Section titled “Recipe B: Generating JSON Request Payloads with JsonOutput”
import groovy.json.JsonOutput
def payload = [
timestamp: System.currentTimeMillis(),
customerId: vars.get("customerId") ?: "cust-1",
actions: [
[action: "PAGE_VIEW", target: "/checkout"],
[action: "CLICK_BUTTON", target: "btn_place_order"]
],
metadata: [
clientVersion: "v2.4.0",
platform: "web"
]
]
String jsonString = JsonOutput.toJson(payload)
vars.put("dynamicPayload", jsonString)

Recipe C: Computing HMAC-SHA256 Signatures

Section titled “Recipe C: Computing HMAC-SHA256 Signatures”

Used for signing requests to AWS, Stripe, or custom cryptographic APIs:

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
String secret = "mySuperSecretKey123"
String dataToSign = vars.get("timestamp") + "." + vars.get("userId")
Mac mac = Mac.getInstance("HmacSHA256")
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256")
mac.init(secretKey)
byte[] rawHmac = mac.doFinal(dataToSign.getBytes("UTF-8"))
String signature = rawHmac.encodeHex().toString()
vars.put("apiSignature", signature)
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
// ISO-8601 UTC timestamp
String isoNow = Instant.now().toString()
// Future expiration timestamp (7 days from now)
String expiresAt = Instant.now().plus(7, ChronoUnit.DAYS).toString()
// Custom formatted date
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withZone(ZoneId.of("UTC"))
String formattedDate = formatter.format(Instant.now())
vars.put("isoNow", isoNow)
vars.put("expiresAt", expiresAt)
vars.put("formattedDate", formattedDate)

Recipe E: Writing Error Payloads to a Debug File

Section titled “Recipe E: Writing Error Payloads to a Debug File”
if (!prev.isSuccessful()) {
String logLine = "${new Date().format('yyyy-MM-dd HH:mm:ss')} | " +
"Thread: ${ctx.getThreadNum()} | " +
"URL: ${prev.getUrlAsString()} | " +
"Code: ${prev.getResponseCode()} | " +
"Body: ${prev.getResponseDataAsString()}\n"
new File("jmeter-failures.log") << logLine
}

  1. Enable Script Caching: Check “Cache compiled script if available” on every JSR223 element.
  2. Never interpolate variables into script text: Write vars.get("token"), not "\${token}".
  3. Use SLF4J parameterized logging: Write log.debug("Processing user {}", userId) instead of string concatenation log.debug("Processing user " + userId) to save memory when debug logging is disabled.
  4. Avoid heavy file I/O: Do not write file logs for every sampler during a high-throughput run. Guard disk operations with if (!prev.isSuccessful()).
  5. Pre-instantiate expensive objects: If reusable across runs, store pre-compiled formatters or cryptographic helpers in static fields or props.
On this page