In REST Assured, why does setting the same custom header twice send two values, and how do you stop it?
answer
- header() appends, it does not assign
- two names are special
- content-type and accept overwrite
- HeaderConfig holds the switch
- cookies have no such switch
basics
~10 sRequest headers merge by default, so both values go on the wire; only Content-Type and Accept overwrite. Move the name into the overwrite set with HeaderConfig.overwriteHeadersWithName, or stop setting it twice.
solid answer
~40 s`header(name, value)` appends rather than assigns. On each call REST Assured asks `HeaderConfig` whether that name overwrites; the shipped answer is yes for exactly two names, `content-type` and `accept`, and no for everything else. So a shared specification that stamps `X-Depot-Id: north-yard` plus a test that sets `X-Depot-Id: south-yard` sends both lines to the dispatch API. Fix it with `config(config().headerConfig(headerConfig().overwriteHeadersWithName("X-Depot-Id")))`, or better, stop setting the header in two places. Two things to know before you reach for the config: `overwriteHeadersWithName(...)` builds a fresh set, so `Content-Type` and `Accept` fall back to merging unless you list them too, and cookies have no equivalent switch — they always accumulate into one `Cookie` header.
code
java · 28 linesimport static io.restassured.RestAssured.config;
import static io.restassured.RestAssured.given;
import static io.restassured.config.HeaderConfig.headerConfig;
class DispatchDepotHeaderTest {
void bothDepotHeadersAreSent() {
given()
.header("X-Depot-Id", "north-yard")
.header("X-Depot-Id", "south-yard")
.when()
.get("/v1/routes") // two X-Depot-Id header lines
.then()
.statusCode(200);
}
void lastDepotHeaderWins() {
given()
.config(config().headerConfig(
headerConfig().overwriteHeadersWithName("X-Depot-Id", "Content-Type", "Accept")))
.header("X-Depot-Id", "north-yard")
.header("X-Depot-Id", "south-yard")
.when()
.get("/v1/routes") // one X-Depot-Id: south-yard
.then()
.statusCode(200);
}
}go deeper
Know that setting a header twice sends it twice, and that Content-Type and Accept behave differently. That is enough to stop you writing a duplicate by accident.
Explain the mechanism: header() appends, HeaderConfig decides per name whether earlier entries are dropped, and only content-type and accept are marked for overwrite out of the box.
Diagnose the shared-specification version of this and weigh the fixes. Show that you know overwriteHeadersWithName replaces the shipped set, and that removing the duplicate source usually beats reconfiguring the library.
Own the convention for who may stamp headers. If one layer owns correlation and depot headers and tests never re-set them, this class of ambiguity disappears without any configuration at all.
## Merge is the default for headers, not overwrite `RequestSpecification.header(name, value)` does not set a slot; it appends an entry to a list of headers. When a second `header("X-Depot-Id", "south-yard")` arrives, REST Assured looks up the name in `HeaderConfig` to decide whether the earlier entry should be discarded. If the configuration says that name overwrites, the earlier entries with that name are dropped; otherwise both survive and the request goes out with two `X-Depot-Id` lines. `HeaderConfig` ships with exactly **two** names marked for overwrite, matched case-insensitively: `content-type` and `accept`. Every other name — including every `X-`-prefixed header a snowplough dispatch API might define — merges. So: - `header("X-Depot-Id", "north-yard").header("X-Depot-Id", "south-yard")` sends both values. - `contentType(ContentType.JSON).contentType(ContentType.XML)` sends only the XML content type. - `accept(ContentType.JSON).accept("text/csv")` sends only `text/csv`. - `header("X-Dispatch-Trace", "a", "b")` — the varargs overload — deliberately records two values under one name in a single call. ## Why this bites in a real suite The two calls are almost never adjacent. The usual shape is a shared `RequestSpecification` that stamps `X-Depot-Id: north-yard` on every request, plus one test that needs the southern depot and sets the header again. Attaching a specification **accumulates** headers rather than replacing them, so the merge default and specification merging point the same way, and the dispatch service sees a header it considers ambiguous. Depending on the service, you get a 400, a silent first-value-wins, or — worst for debugging — a correct-looking response produced from the wrong depot. ## Making a name overwrite ```java given() .config(config().headerConfig(headerConfig().overwriteHeadersWithName("X-Depot-Id"))) .header("X-Depot-Id", "north-yard") .header("X-Depot-Id", "south-yard") .when() .get("/v1/routes"); ``` `overwriteHeadersWithName(String, String...)` takes one name plus any number of extra names and returns a new `HeaderConfig` in which those names replace. Its mirror, `mergeHeadersWithName(String, String...)`, makes the listed names accumulate — which is how you would deliberately send two `Accept` headers if a dispatch endpoint required it. ## The trap inside the switch `HeaderConfig` stores a single map of names to overwrite. `overwriteHeadersWithName(...)` builds a **fresh** map containing only the names you passed — it does not add to the shipped set. So a config built as `headerConfig().overwriteHeadersWithName("X-Depot-Id")` no longer lists `content-type` or `accept`, and those two revert to merging. If you rely on the exception for either, name them explicitly: 1. List every name you want to overwrite in the one call: `overwriteHeadersWithName("X-Depot-Id", "Content-Type", "Accept")`. 2. Or keep the default config and change how the test sets the header instead — for instance by removing the header inside a filter before setting it. 3. Or restructure so the shared specification never sets a header a test needs to override. Option three is usually the healthiest: configuration that silently changes what two other headers do is a poor trade for one overridable name. ## Cookies have no equivalent switch Request cookies always accumulate. `cookie("depot", "north-yard").cookie("depot", "south-yard")` records two cookies, and at send time REST Assured joins every request cookie into a **single** `Cookie` header as `name=value` pairs separated by `"; "`, in insertion order — so the dispatch service receives `Cookie: depot=north-yard; depot=south-yard`. There is no `CookieConfig` update strategy to flip: - `cookie(String, Object, Object...)` adds one entry per value. - `cookies(Map)` and `cookies(Cookies)` append to whatever is already recorded. - The only way to end up with one cookie of a given name is not to record the other one. | what you set twice | what the dispatch API receives | |---|---| | `header("X-Depot-Id", ...)` | two `X-Depot-Id` header lines | | `contentType(...)` | one `Content-Type`, the later value | | `accept(...)` | one `Accept`, the later value | | `cookie("depot", ...)` | one `Cookie` header carrying both pairs | ## Diagnosing it Log the request — the request log prints the headers and cookies actually recorded on the specification, which is where a duplicated name is obvious in one line. If the header is being added by something you do not control, a filter can inspect and remove it before the request is sent. Reaching for the request log first is faster than reasoning about which specification contributed what. ## What to say in an interview Say that headers merge by default, that `Content-Type` and `Accept` are the two exceptions `HeaderConfig` ships, and that `overwriteHeadersWithName(...)` moves a name into the overwrite set. Then add the two details that show you have used it in anger: the switch replaces the default set rather than extending it, and cookies have no such control at all — they always accumulate into one `Cookie` header.
- Why does the example pass Content-Type and Accept to overwriteHeadersWithName as well?Because the method builds a fresh overwrite set from the names you give it rather than adding to the shipped one. Passing only `X-Depot-Id` would leave `content-type` and `accept` out of the set, and calling `contentType(...)` twice would then send two `Content-Type` headers instead of replacing the first.
- What happens in REST Assured if you set the same request cookie name twice?Both are recorded and both are sent. At send time every request cookie is joined into a single `Cookie` header as `name=value` pairs separated by `"; "` in insertion order, so a dispatch call receives `Cookie: depot=north-yard; depot=south-yard`. There is no update-strategy configuration for cookies — the only fix is not to record the second one.
By default a header name works like a guest list: every call adds another line. Content-Type and Accept are name badges instead - writing a second one replaces the first.
saying these in an interview costs you the question
- Assumes header() assigns a slot rather than appending
- Thinks every header overwrites by default
- Cannot name HeaderConfig as the place the behaviour lives
- Believes overwriteHeadersWithName adds to the shipped default set
- Expects a cookie update strategy that does not exist
- Blames the server for rejecting a duplicated request header