skip to content

After a distributed JMeter run, why does the controller's result file hold no response bodies?

level: juniorimportance: should knowfreq 52%

answer

  1. Nothing in the plan asked for this
  2. A remote sending default is doing it
  3. Check the mode property in jmeter.properties
  4. Byte counts survived; the payload did not

basics

~10 s

JMeter's mode property defaults to StrippedBatch, and every Stripped sender blanks each SampleResult's response data on the engine before it is sent. Timings, byte counts and status codes survive; the payload does not.

solid answer

~40 s

In Apache JMeter 6.0.0 the `mode` property decides which `SampleSender` an engine uses, and `SampleSenderFactory` falls back to `StrippedBatch` because nothing in the shipped `jmeter.properties` sets it. The `Stripped` family wraps another sender in `DataStrippingSampleSender`, which replaces each `SampleResult`'s response data with an empty byte array before it leaves the engine. It copies the measured size into the result's `bytes` field first, so the `bytes` and `sentBytes` columns stay honest — only the payload disappears. Since JMeter 3.1 it strips failed samples too, unless you set `sample_sender_strip_also_on_error=false`, which keeps bodies for failures only. If you genuinely need every body on the controller, set `mode=Batch` or `mode=Standard` in the **controller's** `jmeter.properties` and accept the extra network traffic and controller heap that buys.

go deeper

for a junior

Recall that JMeter's mode property has a default you never set, and that the default discards response bodies on the engine. Knowing the property name is most of the answer.

for a middle

Explain the decorator: DataStrippingSampleSender wraps another sender, copies the byte count forward, then blanks responseData. Say which node reads the property and name sample_sender_strip_also_on_error.

for a senior

Show judgment about when to turn stripping off. Keeping only failed bodies is nearly free; keeping every body is a controller heap and network decision you should make deliberately, not by accident.

for a principal

Own the default for the whole fleet. Decide whether teams get bodies back by policy or by exception, and make the controller's property file the single place that choice is expressed and reviewed.

## Where the emptiness comes from `SampleSenderFactory.getInstance()` runs on the **controller** when the test tree is converted for remote execution, and its first statement is `JMeterUtils.getPropDefault("mode", "StrippedBatch")`. Every `mode=` line in the shipped `bin/jmeter.properties` of Apache JMeter 6.0.0 is commented out, so a distributed run that nobody configured is running `StrippedBatch`: a `DataStrippingSampleSender` wrapped around a `BatchSampleSender`. Nothing in your test plan asked for this, and no listener setting undoes it. ## What DataStrippingSampleSender does to each result For every `SampleResult` on its way out of the engine it: 1. copies the measured size forward with `setBytes(getBytesAsLong())`, so the size survives; 2. replaces the payload with an empty array via `setResponseData(new byte[0])`; 3. recurses into sub-results three levels deep, so embedded resources are stripped too; 4. hands the now-small result to the sender it decorates. | Field | After stripping | |---|---| | `responseData` | empty | | `bytes`, `sentBytes` | preserved (copied before the wipe) | | `elapsed`, `latency`, `connectTime` | preserved | | `responseCode`, `responseMessage`, success flag | preserved | | assertion results | preserved | | response headers | preserved (they are a separate field) | That is why the run still produces a usable JTL and a usable HTML dashboard, and why only the body-shaped evidence is missing. ## Failed samples are stripped too `sample_sender_strip_also_on_error` defaults to `true`, a change made in JMeter 3.1. The sender's guard is `if (stripAlsoOnError || result.isSuccessful())`, so flipping the property to `false` strips **successful** samples only and leaves the bodies of failures intact. That is usually the setting you actually want when you are debugging a failing distributed run: you keep the diagnostic payloads and still throw away the 99% of bodies you were never going to read. ## What is unaffected Post-processors and assertions run inside `JMeterThread` — `runPostProcessors()` then `checkAssertions()` — **before** `notifyListeners()` hands the result to the remote wrapper. A Regular Expression Extractor or a Response Assertion on body text therefore behaves exactly as it does in a local run; the engine still had the body when it evaluated them. Only elements that need the body *after* the sample completed, on the controller, lose out. ## Getting bodies back, and what it costs - **`sample_sender_strip_also_on_error=false`** — the surgical option; failures keep their bodies. - **`mode=Batch`** — same batching behaviour, no stripping at all. - **`mode=Standard`** — no stripping and no batching; one RMI call per sample. All three go in the **controller's** `jmeter.properties`, because `mode` and the stripping flag are both read there. Setting them on an engine has no effect. The cost is real: an unstripped run ships every response body across the network and holds it in controller memory until a listener has written it, which is precisely the pressure `StrippedBatch` was made the default to avoid.

  • You need the bodies of failing samples only. What do you change?
    Set `sample_sender_strip_also_on_error=false` on the controller. The sender's guard becomes `if (false || result.isSuccessful())`, so it strips successful samples and leaves failed ones intact. You keep the diagnostic payloads without shipping every successful body back.
  • Does stripping break a Response Assertion that matches on body text?
    No. `JMeterThread` runs post-processors and assertions on the engine before it notifies listeners, so the assertion sees the full body and its verdict is already recorded in the result. Only after-the-fact inspection on the controller loses the body.
  • Do the throughput figures in the HTML dashboard survive stripping?
    Yes. `DataStrippingSampleSender` calls `setBytes(getBytesAsLong())` before blanking the payload, so the `bytes` and `sentBytes` values are carried forward and received-bytes-per-second is still computed correctly.

saying these in an interview costs you the question

  • Blames the listener configuration rather than the remote sending mode
  • Assumes the engines never captured the bodies in the first place
  • Thinks RMI cannot serialise the response byte array
  • Says the bytes column is zeroed too, so throughput is unusable
  • Believes body assertions silently stop working in distributed runs
  • Sets the fix on the engines instead of on the controller