In REST Assured, which value does then().header(name, matcher) check when a header repeats?
answer
- one lookup, not a collection
- the list is scanned in reverse
- cookies follow the same rule
- getValues returns them all
- a List in the map is several matchers
basics
~20 sThe 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.
solid answer
~50 sIt checks the **last** occurrence. The assertion registers a `HeaderMatcher`, which validates against `headers.getValue(name)`, and that lookup reverses the header list and returns the first match it finds — which is the last one received. So on a response carrying `Rental-Warning: tide-window-tight` followed by `Rental-Warning: wetsuit-required`, only the second value is ever matched, and asserting the first one fails even though the service really sent it. Nothing folds the repeated values into one comma-separated string first, so `containsString` will not rescue you either. Cookies behave identically: `cookie(name, matcher)` also takes the last value. The map form does not help — a `List` value in `headers(Map)` registers several matchers that all run against that same single value. To assert on every occurrence, take the parsed `Headers` and match the collection: `headers().getValues(name)` returns all values in wire order, and `headers().getList(name)` returns `Header` objects.
code
java · 19 linesimport static io.restassured.RestAssured.given;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import io.restassured.http.Headers;
Headers headers =
given()
.baseUri("https://api.paddleport.example/v1")
.when()
.get("/rentals/9f31c2")
.then()
.statusCode(200)
// matches the LAST Rental-Warning only
.header("Rental-Warning", equalTo("wetsuit-required"))
.extract().headers();
// every occurrence, in the order the response sent them
assertThat(headers.getValues("Rental-Warning"),
contains("tide-window-tight", "wetsuit-required"));go deeper
Know that a response may repeat a header name and that the plain header() assertion looks at only one of those values, so a passing check is not a claim about all of them.
Explain the mechanism: the assertion resolves the name to a single value by scanning in reverse, and getValues(name) is what exposes the full list.
Be ready to diagnose a suite that passes while a service quietly starts emitting an extra occurrence, and to say which tests should assert the collection instead.
Decide whether repeated response metadata is part of the contract your suites police at all, and where that decision is written down so teams do not each guess.
## The rule: the last occurrence wins When a response carries the same header name more than once, `then().header(name, matcher)` matches against the **last** occurrence and never sees the earlier ones. The kayak-rental API returns two advisories on a booking: ``` HTTP/1.1 200 OK Rental-Warning: tide-window-tight Rental-Warning: wetsuit-required ``` Against that response, `header("Rental-Warning", equalTo("wetsuit-required"))` passes and `header("Rental-Warning", equalTo("tide-window-tight"))` fails, even though both values were sent. The same rule governs cookies: `cookie("name", matcher)` also matches the last value with that name. Nothing is lost — REST Assured parses every occurrence into the response's `Headers` object and keeps them in the order they arrived. It is the *assertion* that is single-valued, because it resolves the name to one value before the matcher ever runs. That distinction is what makes the behaviour worth knowing: the data you want is present, and reaching it is a different call rather than a different matcher. ## Why the code behaves that way - The assertion is a `HeaderMatcher`, and its validation reads `headers.getValue(headerName)` — a single-value lookup, not a collection. - `Headers` delegates to a multi-value entity that **reverses** its list and returns the first matching entry, which is the last one received. - The name comparison is `equalsIgnoreCase`, so capitalisation of the header name is irrelevant to which occurrence is chosen. - Nothing joins repeated values into a comma-separated string first, so `containsString` will not rescue you either — the matcher genuinely sees one value. ## The map form does not change it It is tempting to read `headers(Map)` with a `List` value as "one matcher per occurrence". It is not. - A `List` value in the map registers **several matchers**, and every one of them runs against the same single value — the last occurrence. - So `headers(Map.of("Rental-Warning", List.of(equalTo("tide-window-tight"), equalTo("wetsuit-required"))))` cannot pass: two contradictory expectations are being applied to one value. - Chaining `header(...)` twice with different literals fails for exactly the same reason. ## Asserting on every value The values are all there — they simply are not reachable through the single-value assertion. Pull the parsed `Headers` object out and match the collection instead: | call | returns | order | |---|---|---| | `headers().getValue(name)` | one `String` | the last occurrence | | `headers().getValues(name)` | `List<String>` | wire order, all occurrences | | `headers().getList(name)` | `List<Header>` | wire order, name and value | - `getValues(name)` returns every value in the order the response sent them, so `contains("tide-window-tight", "wetsuit-required")` asserts both the values and their order. - Use `hasItems(...)` instead of `contains(...)` when order is not part of what you are claiming. - `getList(name)` gives `Header` objects when you want the name alongside the value, for example when logging a mismatch. - An unknown name yields an empty list rather than `null`, so a collection matcher such as `hasSize(2)` states "exactly two were sent" without a null check. ## Reading the failure message The failure message is the fastest way to spot that this is what bit you. A failed header assertion prints the matcher, the value it saw, and then every header of the response, one per line: ``` Expected header "Rental-Warning" was not "tide-window-tight", was "wetsuit-required". Headers are: Content-Type=application/json;charset=utf-8 Rental-Warning=tide-window-tight Rental-Warning=wetsuit-required ``` - The name appearing twice in the dump is the tell: the value you expected is right there, and the assertion still failed. - Reading `was "wetsuit-required"` as "the header is wrong" sends you to the service; reading it as "the lookup took the other occurrence" sends you to the assertion, which is where the fix is. ## Choosing which assertion to write Most header expectations are about a single-valued field — a media type, a revision marker, a `Location` — and the ordinary `header(name, matcher)` is exactly right for those. Reach for the collection form deliberately, when the field is one a service is allowed to repeat and your test genuinely claims something about the whole set. A useful habit when a header assertion fails in a way you cannot explain: look at the dumped header list in the failure message. If the name appears twice, you have found the reason, and the fix is to assert the list rather than to change the matcher. The trap is quiet in the other direction too. A test that asserts `header("Rental-Warning", equalTo("wetsuit-required"))` keeps passing on the day the service starts emitting a second, earlier advisory — because the assertion still finds the same last value. If the count itself matters, assert `headers().getValues("Rental-Warning")` with `hasSize(...)`; nothing else in the DSL will notice a new occurrence appearing in front of the one you pinned.
- Does the same last-wins rule apply to then().cookie(name, matcher)?Yes. Cookie lookup goes through the same multi-value structure, so `cookie("cookie1", equalTo(...))` matches the last cookie with that name and the earlier ones are invisible to it. The extract-side `detailedCookies()` returns the whole `Cookies` object, and `getValues(name)` on it exposes every value.
- Can you assert that a header was sent exactly twice?Not with `header(name, matcher)`, which only ever sees one value. Extract the parsed headers and assert on the collection: `headers().getValues("Rental-Warning")` with `hasSize(2)`, or `contains(...)` when the order is part of the claim. An unknown name yields an empty list rather than null, so no null check is needed.
- Why does chaining header(name, equalTo("a")).header(name, equalTo("b")) never pass?Both assertions resolve the same name to the same single value — the last occurrence — so at most one of two different literals can match. Repeating the call registers a second expectation against the same value rather than walking to the next occurrence.
It behaves like writing duplicate keys into a map: only the last write is visible to a lookup, even though every line really was received.
saying these in an interview costs you the question
- Thinking repeated headers are joined into one comma-separated value
- Expecting a List in headers(Map) to match one occurrence each
- Believing the first occurrence is the one that gets matched
- Chaining two header() calls to cover two values of one name
- Assuming a passing assertion proves the header was sent only once