In REST Assured, where does then().log().ifValidationFails() write while every expectation still passes?
answer
- the stream is swapped, not skipped
- the text exists before anyone reads it
- held in memory until something throws
- then() validates eagerly, call by call
basics
~20 sREST Assured swaps the print stream for a ByteArrayOutputStream registered in an internal log repository, so the text accumulates in memory. A default validation-failure listener flushes that buffer when a then() expectation throws. A passing call discards it silently.
solid answer
~50 sIt writes into memory, not to nowhere. `ifValidationFails` builds a `ByteArrayOutputStream`, wraps it in a `PrintStream`, registers the buffer with an internal log repository and then does the ordinary log work against that stream instead of the configured one. The response text is therefore produced immediately, exactly as `then().log().all()` would produce it, and simply has no reader yet. When an expectation in the `then()` chain fails, REST Assured fires its validation-failure listeners; the default one reads both buffers back and prints them — request first, then response — to the stream `LogConfig` is configured with. Nothing else flushes them, so a green call drops its buffer on the floor. The practical consequence is ordering: because a `then()` chain validates eagerly as each expectation is called, the log call must come **before** the assertions it is meant to explain.
code
java · 15 linesimport static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.hasSize;
// prints nothing: statusCode() throws before the buffer is registered
given().baseUri("https://ladder.curlingclub.test")
.when().get("/rinks/rink-3/ladder")
.then().statusCode(200)
.log().ifValidationFails();
// prints the response: the buffer exists before the first expectation runs
given().baseUri("https://ladder.curlingclub.test")
.when().get("/rinks/rink-3/ladder")
.then().log().ifValidationFails()
.statusCode(200)
.body("rungs", hasSize(8));go deeper
You mostly need the outcome: nothing prints while the test passes, and the log call goes before the assertions in a then() chain. Recognising the ordering trap in review is enough at this level.
Be able to describe the swap concretely — a ByteArrayOutputStream registered with a log repository, formatted immediately, flushed by a failure listener — and explain why the request side is order-independent while the response side is not.
Reason about the cost honestly: formatting happens on every call, so this buys silence and not speed, and a very large payload is briefly held in memory. Say when you would narrow the detail instead.
Decide what your suites depend on. A mechanism whose behaviour turns on chain order is a code-review burden, so consider whether the global switch plus a shared specification is the safer default across many teams.
## The mechanism, not the magic It is tempting to imagine `ifValidationFails` as a conditional print — that REST Assured somehow knows the future and decides not to log. It does not. **The stream is swapped, not the decision.** Every one of the log calls in the DSL ends up doing the same work: formatting a request or a response into text and writing it to a `PrintStream`. `ifValidationFails` differs in one respect only — the `PrintStream` it writes to wraps a `ByteArrayOutputStream` held in memory rather than the console. The sequence on the response side is: 1. `then().log().ifValidationFails()` creates a `ByteArrayOutputStream` and a `PrintStream` over it. 2. It registers that buffer with an internal log repository attached to the response specification. 3. It performs the normal response print against that stream, so the text exists from that moment on. 4. If a later expectation fails, REST Assured fires its validation-failure listeners; the default listener reads the request buffer and the response buffer back as strings and prints whichever are non-empty to the log stream `LogConfig` is configured with, request first. 5. If nothing fails, nobody ever reads the buffer and it is garbage collected with the rest of the call. ## Why ordering matters on the response side A `then()` chain in REST Assured validates **eagerly**. The response is already in hand by the time `then()` is reached, so each expectation — `statusCode(200)`, `body(...)`, `header(...)` — is evaluated as it is called rather than collected and run at the end. That single fact produces the most common way this feature appears broken: - `then().log().ifValidationFails().statusCode(200)` — the buffer is registered first, `statusCode` then fails, the listener finds the buffer and prints it. Works. - `then().statusCode(200).log().ifValidationFails()` — `statusCode` throws before the log call is ever reached. There is no registered buffer, so the listener has nothing to print, and the run looks as if the feature is switched off. So on the response side, chain position is load-bearing: put `log().ifValidationFails()` first in the `then()` chain, always. ## Why ordering does not matter on the request side The request-side twin behaves differently, and for a good reason. `given().log().ifValidationFails()` also builds a buffer and registers it, but the thing it attaches is a `RequestLoggingFilter` writing to that buffer. Filters do not run while the `given()` chain is being assembled — they run when the request is actually sent, after the whole specification is built. So: - the request log call can appear anywhere in `given()`; - the request text is produced at send time, reflecting the fully built request including resolved path parameters; - and it is therefore always available to the listener if a later expectation fails. The asymmetry is worth remembering as a pair: **response-side buffering happens where you write the call, request-side buffering happens when the request is sent.** ## What the listener will and will not fire on The flush is not a general exception hook. It is fired from inside REST Assured's response validation, which means: - A failing expectation declared in the `then()` chain fires it. - A throwable raised while those expectations are being evaluated — a parse error on a malformed ladder payload, for instance — fires it too, before the throwable is rethrown. - A connection refusal or a TLS failure does **not**, because the exception escapes before `then()` is reached and no validation ever begins. - A JUnit or AssertJ assertion written after `extract()` does not, because it is outside REST Assured altogether. ## What it costs For a suite hammering `/rinks/{rinkId}/ladder` a few hundred times, the accounting is easy: - One `ByteArrayOutputStream` per logged side per call, sized to the text it holds. - The formatting work happens whether or not anything fails, so the CPU cost is the same as always-on logging; only the I/O and the console noise are saved. - Nothing is retained beyond the call, because the repository is attached to that call's specification rather than to a static. - A very large response body is held in memory for the life of the call, which is the one case worth narrowing with a `LogDetail` argument. That is the whole trade: you pay the formatting cost on every request to buy silence on the green ones and a complete record of the red one.
- Does the same buffering happen on the request side, and does chain order matter there too?Yes to the buffer, no to the order. `given().log().ifValidationFails()` registers a `RequestLoggingFilter` that writes into its own `ByteArrayOutputStream`, and filters only run when the request is sent — after the whole `given()` chain has been built. So that call can sit anywhere in `given()`, unlike its response-side twin.
- What happens to the buffer when the call fails before a response arrives?Nothing prints. The flush is driven by a failure listener fired inside response validation, so a connection refusal or a TLS failure that throws before `then()` is reached never triggers it. Anything thrown while expectations are actually being evaluated does fire it, including a parse error on the response body.
It works like a dashcam loop: the camera is recording the whole time, and the footage is only written out as a saved clip when the impact sensor fires.
saying these in an interview costs you the question
- Thinks the response is re-fetched and printed after the failure
- Believes the log call can go anywhere in the then() chain
- Assumes buffering delays the request as well as the print
- Thinks a connection timeout also flushes the buffer
- Claims the buffer carries over into the next test