skip to content

Response Consumption

What a REST Assured test does with a response once it has arrived: the expectations it states about it, and the values it reads back out of it. Most real suite defects live in this half of the chain.

part ofREST Assuredoverview, primer and where to startread it →
on this pageshow

explore

questions

27

In REST Assured, how do you assert on a response header inside the then() block?

level: juniorimportance: must knowfreq 70%

answer

  1. four overloads, one interface
  2. literal, matcher, mapping function, response-aware
  3. headers(Map) for several at once
  4. contentType has its own method
  5. failure dumps every header

basics

~20 s

REST Assured's then() block asserts headers with header(name, expectedValue) for an exact string, header(name, Matcher) for anything looser, and headers(Map) for several at once. Content-Type has its own method, contentType(ContentType.JSON). A failed check throws AssertionError and prints every header received.

solid answer

~50 s

`ValidatableResponseOptions`, the interface behind `then()`, declares four header overloads. `header("Rental-Catalog-Rev", "rev-2026-04-11")` compares the value as an exact string; `header("Rental-Catalog-Rev", startsWith("rev-2026"))` takes any Hamcrest matcher; `header("RateLimit-Remaining", Integer::parseInt, greaterThan(0))` maps the raw text through a `Function` first, so a numeric header is compared as a number; and `header("Location", response -> endsWith(response.path("rentalId")))` takes a `ResponseAwareMatcher` that can read the response it is validating. `headers(Map)` and the varargs `headers("A", "1", "B", matcher)` register several at once, mixing literals and matchers freely; a plain literal is simply wrapped in `equalTo`. `Content-Type` has a method of its own, `contentType(...)`, because the media type also decides how REST Assured parses the body. The name lookup is case-insensitive and the matcher always receives the raw `String` value, parameters included. A failure throws `AssertionError` naming the header, the expectation, the value seen and every header the response carried.

code

java · 16 lines
java
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import io.restassured.http.ContentType;

given()
    .baseUri("https://api.paddleport.example/v1")
    .queryParam("launchSite", "harbour-quay")
.when()
    .get("/kayaks")
.then()
    .statusCode(200)
    .contentType(ContentType.JSON)
    .header("Rental-Catalog-Rev", startsWith("rev-2026"))
    .header("RateLimit-Remaining", Integer::parseInt, greaterThan(0))
    .headers("RateLimit-Limit", "120",
             "Cache-Control", containsString("no-store"));

go deeper

for a junior

Be ready to write a header expectation from memory: name plus literal, or name plus Hamcrest matcher, chained after statusCode in the then() block.

for a middle

Explain what the matcher actually receives - the raw String value, looked up case-insensitively - and when the mapping-function overload is the right one.

for a senior

Show judgment about which response metadata belongs in a suite at all, and how to keep header expectations from failing on harmless value drift.

for a principal

Own the convention: which headers form the contract every service test must assert, and where those expectations live so hundreds of tests do not each invent their own.

## What a header assertion actually reads `then()` hands you a `ValidatableResponse`, and every check you chain onto it is declared on `ValidatableResponseOptions`. A header check registers the header name together with a Hamcrest matcher; when REST Assured validates the response it looks that name up in the parsed `Headers` object and runs your matcher against the value it finds. Two consequences fall straight out of that. The lookup is **case-insensitive**, so `header("content-type", ...)` and `header("Content-Type", ...)` behave identically. And unless you interpose a mapping function, the value handed to your matcher is the raw **`String`** — the field value exactly as it arrived, parameters and all. Nothing here touches the outgoing call. `given().header(...)` writes a header on `RequestSpecification`; `then().header(...)` checks one on `ValidatableResponseOptions`. Same word, opposite sides of the DSL. A catalogue call against the kayak-rental API answers like this: ``` HTTP/1.1 200 OK Content-Type: application/json;charset=utf-8 RateLimit-Remaining: 118 Rental-Catalog-Rev: rev-2026-04-11 ``` ## The four `header(...)` overloads | overload | what you pass | what runs | |---|---|---| | `header(String, String)` | a literal value | wrapped in `equalTo`, exact string comparison | | `header(String, Matcher<?>)` | any Hamcrest matcher | matcher applied to the raw value | | `header(String, Function<String,V>, Matcher<? super V>)` | a converter, then a matcher | value mapped first, then matched | | `header(String, ResponseAwareMatcher<R>)` | a lambda over the response | the matcher is built from the response itself | - `header("Rental-Catalog-Rev", "rev-2026-04-11")` is the exact-string form; it is the one that breaks first, and sometimes for no reason worth failing a build over. - `header("Rental-Catalog-Rev", startsWith("rev-2026"))` accepts any Hamcrest matcher, which is how you assert a shape instead of a value. - `header("RateLimit-Remaining", Integer::parseInt, greaterThan(0))` maps the raw text through a `Function` before matching, so a numeric field is compared as a number rather than as characters. - `header("Location", response -> endsWith("/rentals/" + response.path("rentalId")))` builds the expectation out of the very response under validation, which is how you assert that a header agrees with the body beside it. ## Checking several headers in one call - `headers(Map<String, ?>)` takes a map whose values may be plain strings or matchers, mixed freely in the same map. - `headers("RateLimit-Limit", "120", "Rental-Catalog-Rev", startsWith("rev-"))` is the varargs spelling of the same thing: name, value, name, value. - A plain string value is wrapped in `equalTo`, so the map form is no looser than the single-header form — it is shorthand, not a different comparison. - Each entry becomes its own registered check, so a map of four headers is four assertions and four possible failure messages. ## `Content-Type` gets a method of its own Content type is rarely asserted through `header("Content-Type", ...)`, because REST Assured gives it `contentType(ContentType)`, `contentType(String)` and `contentType(Matcher<? super String>)`. Those three are deliberately not equivalent in strictness: the `ContentType` enum form resolves the media type into a family, the `String` form is a prefix comparison, and the `Matcher` form sees the raw header including any `;charset=utf-8`. Reach for whichever strictness you actually mean. ## Reading the failure When a header check fails, REST Assured throws an `AssertionError` whose message names the header, prints the matcher's description, prints the value it saw, and then dumps every header the response carried, one per line: ``` Expected header "Rental-Catalog-Rev" was not "rev-2026-05", was "rev-2026-04-11". Headers are: Content-Type=application/json;charset=utf-8 RateLimit-Remaining=118 Rental-Catalog-Rev=rev-2026-04-11 ``` - A header that is simply absent is reported with the value `"null"` — there is no separate "header not present" error, so read the dumped list to tell "wrong value" from "never sent". - The dump is what makes a header assertion cheap to debug: you do not need to rerun with logging to find out what the service actually returned. ## What is worth asserting Header checks earn their place on the metadata that carries contract meaning and that a body check can never see — the media type a client will parse with, a rate-limit budget, a `Location` pointing at the resource a `POST` created, a catalogue revision a caching client depends on. They are also where a lot of brittle tests come from, so prefer a matcher over a literal for anything that legitimately moves, use the mapping-function form for anything numeric, and leave headers that no consumer reads out of the suite entirely.

  • Does then().header(name, matcher) care about the case of the header name?
    No. REST Assured resolves the name against the parsed `Headers` object with a case-insensitive comparison, so `header("content-type", ...)` and `header("Content-Type", ...)` are the same assertion. Only the name is case-insensitive; the value is handed to your matcher exactly as it arrived, so a matcher such as `equalTo("NO-STORE")` will still fail against `no-store`.
  • What does then().header(name, expectedValue) report when the response never sent that header?
    It fails with the value rendered as `"null"` — `Expected header "Rental-Catalog-Rev" was not "rev-2026-04-11", was "null"` — followed by a dump of every header the response did carry. There is no distinct missing-header error, so the dumped list is how you tell an absent header from a wrong one.
  • Why prefer header(name, Integer::parseInt, greaterThan(0)) over header(name, equalTo("118"))?
    The mapping-function overload converts the raw value before matching, so you assert the property you actually care about — a remaining budget above zero — instead of pinning one exact count that changes on every run. The literal form is a string comparison and turns a healthy value into a failure the moment the number moves.

saying these in an interview costs you the question

  • Thinking given().header() and then().header() are the same method
  • Assuming a header matcher receives a parsed number rather than a String
  • Believing header names must match the response's capitalisation exactly
  • Using an exact literal for values that legitimately change every run
  • Expecting a missing header to produce its own distinct error type
open as a page

In REST Assured, why can a test of a rejected request pass even when the server returns 200?

level: juniorimportance: must knowfreq 66%

basics

~20 s

REST Assured validates only when at least one expectation exists. A call with no then() block, or a then() that sets no status, status line, body, header, cookie, content type or time check, returns the response and never fails.

open as a page

In REST Assured, how does then().body(path, matcher) differ from then().body(matcher) with no path?

level: middleimportance: must knowfreq 66%

basics

~20 s

REST 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.

open as a page

In REST Assured, what happens to a then().statusCode(201).extract().path(...) chain when the status check fails?

level: middleimportance: must knowfreq 62%

basics

~20 s

Nothing after the failing check runs. REST Assured validates each then() expectation the moment you call it, so a failed status assertion throws an AssertionError there and then; extract() is never reached and your local variable is never assigned.

open as a page

In REST Assured, which value does then().header(name, matcher) check when a header repeats?

level: middleimportance: must knowfreq 46%

basics

~20 s

The last one. REST Assured's header assertion reads Headers.getValue(name), which scans the response headers in reverse and returns the final case-insensitive match, so earlier occurrences are never matched. Extract headers().getValues(name) to assert on every value the response carried.

open as a page

In REST Assured, why is calling extract().path() six times on one response worse than holding a JsonPath?

level: seniorimportance: must knowfreq 51%

basics

~20 s

Each extract().path(...) call builds a fresh path object over the body, so six calls parse the same document six times. Hold one extract().jsonPath() instead: it caches its parse after the first read and takes a JsonPathConfig.

open as a page

In REST Assured, why does then().contentType(ContentType.JSON) accept a vendor +json media type?

level: seniorimportance: must knowfreq 44%

basics

~20 s

Because the enum overload is a family check. REST Assured strips any parameters, then resolves the media type through ContentType.fromContentType, which maps anything ending in +json, plus application/javascript, text/json and text/javascript, to JSON. The String and Matcher overloads are stricter.

open as a page

In REST Assured, why does an XML path with a namespace prefix return an empty string?

level: seniorimportance: must knowfreq 45%

basics

~20 s

REST Assured resolves a path prefix through XmlConfig's declared namespaces, not the document's. Until declareNamespace(prefix, uri) binds that exact URI, a prefixed step matches nothing and reads back as an empty string; setting namespaceAware alone does not help.

open as a page

In REST Assured, what do rootPath, appendRootPath, detachRootPath and noRootPath do to body expectations?

level: juniorimportance: should knowfreq 47%

basics

~20 s

REST Assured's rootPath sets a prefix that every later body(path, matcher) call in the same then() chain is joined onto. appendRootPath extends that prefix, detachRootPath strips a suffix off it, and noRootPath clears it. They affect body expectations only.

open as a page

In REST Assured, what can then().extract() give you besides a value from the response body?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Extraction is not only about body fields. The same ExtractableResponse yields statusCode(), statusLine(), contentType(), header(name), headers(), cookie(name), cookies(), detailedCookie(name), detailedCookies(), sessionId(), time(), timeIn(TimeUnit) and response(), plus whole-body forms such as asString(), asPrettyString(), asByteArray() and asInputStream(). One interface, not several.

open as a page

In REST Assured, why does then().body(path, equalTo(0.82)) fail on a JSON decimal?

level: juniorimportance: should knowfreq 45%

basics

~20 s

REST Assured parses a JSON decimal into a Float by default. Hamcrest's equalTo compares with equals, so a Java double literal like 0.82 never matches the parsed Float. Write 0.82f instead, or change the configured number return type.

open as a page

In REST Assured, how do you read an XML element and its attributes with GPath?

level: juniorimportance: should knowfreq 52%

basics

~20 s

REST Assured evaluates a dotted Groovy GPath over the parsed XML tree: each dot is a child-element step, an attribute step carries @, and text() returns character data. Indexes, size() and closures work; a missing attribute reads as null.

open as a page

In REST Assured, where do withArgs(...) and withNoArgs() live, and what do they fill in?

level: middleimportance: should knowfreq 38%

basics

~20 s

withArgs and withNoArgs are static methods on io.restassured.RestAssured that return a List of Argument. You pass them into body, rootPath or appendRootPath, which fills the placeholders in the merged path with String.format. They are not methods on the response.

open as a page

In REST Assured, why does the JsonPath expression $.glossary.entry[0].term return null?

level: middleimportance: should knowfreq 48%

basics

~20 s

REST Assured's JsonPath speaks Groovy GPath, not the Jayway JsonPath syntax other tools use. GPath has no dollar-sign root, so that fragment is read as an ordinary property name, finds nothing, and the chain collapses to null. Write glossary.entry[0].term instead.

open as a page

In REST Assured, how do you assert that a response arrived inside a time budget?

level: middleimportance: should knowfreq 36%

basics

~20 s

Use then().time(Matcher) to match the round trip in milliseconds, as in time(lessThan(2000L)). The matcher is typed Matcher<Long>, so long literals are required. Add a TimeUnit for another unit, remembering the conversion truncates. REST Assured records the figure on every call.

open as a page

In REST Assured, why does then().statusCode(200).onFailMessage("...") never print your message?

level: middleimportance: should knowfreq 38%

basics

~20 s

Every expectation on a then() chain validates the moment it is called, so statusCode(200) throws before onFailMessage is reached and the message field is still unset. Put onFailMessage first, immediately after then(), ahead of any expectation it should annotate.

open as a page

In REST Assured, what does then().statusCode(anyOf(is(409), is(422))) allow that statusCode(409) does not?

level: middleimportance: should knowfreq 58%

basics

~20 s

The matcher overload accepts a set or range of codes instead of one exact value, so a contract that legitimately answers with either 409 or 422 still passes. It also changes the failure text, which then names every accepted code.

open as a page

In REST Assured, what does XmlPath.CompatibilityMode.HTML change about parsing a body?

level: middleimportance: should knowfreq 38%

basics

~20 s

XmlPath.CompatibilityMode swaps the parser, not the path language. Its XML constant builds an XmlSlurper from XmlConfig's validating, namespaceAware and allowDocTypeDeclaration flags; its HTML constant wraps the same slurper around tagsoup, which repairs ill-formed markup instead of rejecting it.

open as a page

In REST Assured, how do you assert that one field of a response matches another field of the same response?

level: seniorimportance: should knowfreq 36%

basics

~20 s

REST Assured's ResponseAwareMatcher lets a body or header expectation build its matcher from the response itself. Its one method, matcher(response), returns an ordinary Hamcrest matcher. RestAssuredMatchers ships equalToPath, startsWithPath, endsWithPath and containsPath for the common cases.

open as a page

In REST Assured, how do you put a runtime value into a JsonPath GPath expression safely?

level: seniorimportance: should knowfreq 34%

basics

~20 s

A JSON GPath expression is compiled and run as Groovy, so concatenating a runtime value into it is code injection. Bind the value safely with JsonPath.param(name, value) instead. The expression then references it by name inside the closure.

open as a page

In REST Assured, what can a FailureConfig failure listener do with a response that then() rejected?

level: seniorimportance: should knowfreq 32%

basics

~20 s

A failure listener receives the request specification, the response specification and the full Response just before the AssertionError is thrown, so it can record or forward them. It cannot change the verdict, edit the message, or retry.

open as a page

In REST Assured, which XmlConfig switches decide whether a DOCTYPE in a response parses?

level: seniorimportance: should knowfreq 31%

basics

~20 s

XmlConfig.allowDocTypeDeclaration(boolean) decides it, and its default is false, so a body carrying a DOCTYPE fails to parse. disableLoadingOfExternalDtd() is a separate switch that only stops the parser fetching the referenced DTD, and validating(boolean) likewise defaults to false.

open as a page

Your REST Assured helpers all end in extract().response() and assert later — what does that convention cost?

level: principalimportance: should knowfreq 38%

basics

~20 s

You give up the ordering guarantee. Nothing has been asserted when the value is produced, so a failed call yields null, the failure surfaces later as an unrelated error, and you lose REST Assured's own mismatch message.

open as a page

In REST Assured, what does MatcherConfig.errorDescriptionType change in a failed body assertion's message?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

MatcherConfig.errorDescriptionType picks who describes the actual value when a REST Assured body expectation fails. The default, REST_ASSURED, prints the extracted value itself; HAMCREST calls the matcher's own describeMismatch instead. The enum has exactly those two constants.

open as a page

In REST Assured, which settings control how JsonPath materialises JSON numbers?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

REST Assured's numberReturnType controls how JSON numbers are materialised, with four values: FLOAT_AND_DOUBLE (the default), BIG_DECIMAL, DOUBLE and BIG_INTEGER. Set it on JsonConfig for the DSL or JsonPathConfig standalone. Both also cap numeric literals via numberLengthLimit.

open as a page

Your REST Assured suite asserts through long GPath closures — how much logic belongs in the expression?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Keep a GPath expression to one filter or projection you can read at a glance. Move multi-stage computation into Java. The path is an unchecked string compiled as Groovy, so mistakes surface at runtime, never at compile time.

open as a page