In REST Assured, how does then().body(path, matcher) differ from then().body(matcher) with no path?
answer
- two overloads, only one parses
- no path means raw body string
- path form: alternating key, matcher pairs
- no-path form: more matchers, same body
- hasXPath is the one exception
basics
~20 sREST Assured's body(path, matcher) evaluates the path against the parsed response body and hands the extracted value to the matcher. The no-path body(matcher) form applies the matcher to the whole raw body string instead. Both accept any Hamcrest matcher.
solid answer
~50 s`io.restassured.response.ValidatableResponse` declares both, and they reach different machinery. `body(String path, Matcher, Object...)` runs the path through the JSON or XML path engine, extracts a value, and asserts the matcher against *that value* — so `body("headCount", equalTo(24))` compares an `Integer`, not text. `body(Matcher, Matcher...)` has no path, so nothing is parsed: REST Assured matches against `response.asString()`, the raw body as it arrived. That makes it right for a plain-text reply or a `containsString` smoke check, and a poor way to check JSON. The trailing varargs differ too: on the path form they are alternating *key, matcher* pairs, as in `body("lotId", equalTo("LOT-4417"), "breed", equalTo("Angus"))`; on the no-path form they are simply more matchers, every one applied to the same raw body. One exception: REST Assured detects a `hasXPath` matcher and feeds it a parsed DOM even with no path.
code
java · 15 linesimport static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.*;
// path form: the matcher sees the value the path expression returned
when().get("/lots/LOT-4417")
.then()
.body("lotId", equalTo("LOT-4417"),
"breed", equalTo("Angus"),
"headCount", equalTo(24),
"currentBid.bidderId", startsWith("BID-"));
// no-path form: every matcher sees response.asString() untouched
when().get("/lots/LOT-4417/state")
.then()
.body(containsString("SOLD"), not(containsString("PASSED_IN")));go deeper
Be ready to write one body("currentBid.amount", ...) expectation and say where the value being compared came from — the path pulled it out of the parsed body.
Explain the mechanical split: the path form parses and asserts on the extracted value, the no-path form matches response.asString() untouched, and the trailing varargs mean different things on each.
Show judgment about which form belongs in a real suite. Whole-body string matching breaks on whitespace, key order and additive schema changes, so keep it for text replies and smoke checks and say why in review.
Own the convention: decide where whole-body matching is allowed at all, what reviewers should push back on, and how failure output quality feeds into whether a red CI run is diagnosable without a rerun.
`io.restassured.response.ValidatableResponse` declares seven `body(...)` overloads, but two of them carry the whole story: `body(String path, Matcher<?> matcher, Object... additionalKeyMatcherPairs)` and `body(Matcher<?> matcher, Matcher<?>... additionalMatchers)`. Inside a `then()` chain they read almost identically, and they reach completely different machinery. Everything below uses a cattle-auction bidding API whose `GET /lots/LOT-4417` returns: ```json { "lotId": "LOT-4417", "breed": "Angus", "headCount": 24, "reserveMet": true, "currentBid": { "amount": 1875, "bidderId": "BID-88" } } ``` ## The path form parses, then matches the extracted value When you pass a path, REST Assured records an internal body matcher whose *key* is your path string. At validation time it picks a path engine from the response content type — the JSON assertion for JSON, the XML assertion for XML — evaluates the expression against the parsed document, and calls the Hamcrest matcher against whatever came back. The matcher therefore never sees JSON text: - `body("breed", equalTo("Angus"))` compares a `String`. - `body("headCount", equalTo(24))` compares an `Integer`. - `body("reserveMet", is(true))` compares a `Boolean`. - `body("currentBid.bidderId", startsWith("BID-"))` compares the nested `String`. The matcher is an ordinary `org.hamcrest.Matcher`. REST Assured ships no comparison vocabulary of its own for values — it supplies the extracted value and lets Hamcrest decide. Anything from `org.hamcrest.Matchers` works here, and so do the few response-oriented matchers REST Assured adds in `io.restassured.matcher.RestAssuredMatchers`. ## The no-path form never parses anything `body(Matcher, Matcher...)` records a body matcher whose key is `null`. REST Assured reads that as *no path parsing required* and matches against `response.asString()` — the raw body, byte for byte as it arrived. So `body(equalTo("LOT-4417 SOLD"))` is a reasonable assertion against a `text/plain` confirmation, and `body(containsString("LOT-4417"))` is a legitimate smoke check. Writing one giant string equality against a JSON payload is not: it breaks on whitespace, on key order, and on any field the server adds later, and the failure prints two long strings side by side. ## The two forms compared | | `body(path, matcher)` | `body(matcher)` | |---|---|---| | Body parsed? | yes, by the JSON or XML path engine | no | | Matcher sees | the value the path returned | `response.asString()` | | Typical matcher | `equalTo(24)`, `hasItem(...)` | `containsString(...)`, `matchesXsd(...)` | | Trailing varargs | alternating key, matcher pairs | more matchers for the same body | | Fails when | the path is wrong or the value differs | any byte of the body differs | ## The trailing varargs are not the same varargs This is the detail interviewers actually probe, because both signatures end in a vararg and the meanings diverge: 1. On the path form, `additionalKeyMatcherPairs` is read as alternating key and matcher, so `body("lotId", equalTo("LOT-4417"), "breed", equalTo("Angus"), "headCount", equalTo(24))` registers three independent path expectations in a single call. 2. On the no-path form, `additionalMatchers` is just more matchers, and every one of them is applied to the same raw body string. Confusing them compiles cleanly — the path form's vararg is `Object...`, so it accepts anything — and then fails at runtime while REST Assured tries to pair the values up. ## `hasXPath` is the documented exception There is exactly one case where a matcher passed with no path still receives a parsed document. Before matching, REST Assured asks whether the matcher is an `org.hamcrest.xml.HasXPath`, or whether its description text mentions XPath — a nested matcher counts. If so, it builds a DOM with `DocumentBuilderFactory`, honouring `XmlConfig`'s namespace-aware flag and any features you set, and matches against the root element instead of the string. That is why `body(hasXPath("/lot/breed", containsString("Ang")))` works with no path argument. ## Practical guidance - Reach for the path form by default: its failure message names the path and prints the actual value, which is what you want in a CI log. - Reserve the no-path form for non-structured bodies, for `containsString` smoke checks, and for the schema and DTD matchers designed to take a whole document. - Never build a whole-payload assertion out of one string equality; it is the most brittle expectation an API suite can carry. - Remember that both forms are seams onto Hamcrest — the expressiveness comes from the matcher you pass, not from REST Assured. - Watch the numeric type the matcher expects: an `Integer` value of 24 does not satisfy `equalTo(24L)`, and the failure reads `Expected: <24L>` over `Actual: <24>`. The short version an interviewer wants back: a path means *parse and extract, then match the value*; no path means *match the raw text*; and the varargs at the end change meaning between the two.
- What does REST Assured do with the trailing arguments of body("lotId", equalTo("LOT-4417"), "breed", equalTo("Angus"))?It reads them as alternating key and matcher and registers each pair as its own independent path expectation, exactly as if you had written a separate `body(...)` call for each. The root path, if one is set, is merged onto every key in the list, not only the first.
- Why can then().body(hasXPath("/lot/breed")) work when you give it no path string?REST Assured inspects the matcher before matching. If it is an `org.hamcrest.xml.HasXPath`, or its description text mentions XPath, REST Assured builds a DOM from the body with `DocumentBuilderFactory` under `XmlConfig` and matches against the root element rather than against the raw string.
- When is matching the whole body as a string genuinely the right call?When the body is not structured data: a `text/plain` confirmation, an identifier echoed back, or a smoke check that some token appears at all. It is also how the document-level matchers such as `matchesXsd` and `matchesDtd` are meant to be passed, since they want the entire payload.
saying these in an interview costs you the question
- Thinks body(equalTo(...)) with no path compares parsed JSON fields
- Assumes the trailing varargs mean the same thing on both overloads
- Says REST Assured ships its own value-comparison vocabulary rather than using Hamcrest
- Checks a JSON payload by comparing the whole body to one string literal
- Believes the path form always hands the matcher a String