In REST Assured, which settings does given().spec(...) overwrite and which does it accumulate?
answer
- two lists, not one behaviour
- one value assigned, many values combined
- named path params add, unnamed replace
- same filter instance added once
- config swapped per config object
basics
~20 sSpecificationMerger overwrites the scalar request settings — port, baseUri, basePath, authentication scheme, request body, urlEncodingEnabled, proxy, method and path — while it accumulates the collections: parameters, named path parameters, multiparts, cookies, headers and filters. Duplicate filter instances are added once.
solid answer
~50 s`SpecificationMerger.merge` splits every field into one of two behaviours. **Assigned from the incoming spec:** `port`, `baseUri`, `basePath`, `authenticationScheme`, `requestBody`, `urlEncodingEnabled`, `allowContentType`, `addCsrfFilter`, `proxySpecification`, `method`, `path`, and the unnamed positional path-parameter tuples. **Added to what is already there:** request, query and form parameters, named path parameters, multiparts, cookies, headers and filters. The response-side overload splits the same way: `contentType`, `bodyRootPath`, `expectedStatusCode`, `expectedStatusLine`, `expectedResponseTime` and the response log detail are overwritten, while `bodyMatchers`, `cookieAssertions`, `headerAssertions` and registered parsers accumulate. Two details are easy to miss: the same `Filter` instance present on both sides is added only once, and configuration is swapped whole config object at a time by its `isUserConfigured()` flag rather than setting by setting. The rule of thumb is that single-valued fields are assigned and collection-valued ones combine — with the one exception of unnamed path parameters, which are an ordered tuple and are still replaced.
code
java · 16 linesimport io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import static io.restassured.RestAssured.given;
RequestSpecification trekSpec = new RequestSpecBuilder()
.setBasePath("/v2")
.addQueryParam("region", "andes")
.build();
// basePath is overwritten: "/v2" replaces "/v1", it is not appended
// query params accumulate: region AND difficulty are both sent
given().basePath("/v1")
.queryParam("difficulty", "alpine")
.spec(trekSpec)
.when()
.get("/treks"); // GET /v2/treks?difficulty=alpine®ion=andesgo deeper
Know that spec() is not a simple copy: single-valued settings such as the base URI are replaced, while headers and parameters from both sides are sent together.
Be able to sort a field into the right bucket on demand and explain the rule behind the split — one-valued fields are assigned, collections combine, with unnamed path parameters as the exception.
Show how the split explains real symptoms: a body vanishing, a base path replaced rather than nested, or a request logged twice because two filter instances both survived the merge.
Decide what a shared specification is allowed to own. Letting one set a body or a method makes every attachment a potential silent overwrite, so most suites restrict shared specs to hosts, headers and filters.
## One method, two behaviours `given().spec(trekSpec)` looks like a single operation but it is two, applied field by field. `SpecificationMerger.merge(thisOne, with)` walks the specification and, depending on the field, either **assigns** the incoming value over the existing one or **adds** the incoming entries to what is already there. Knowing which list a field is on is the whole skill; nothing else about specification attachment is subtle. ## The overwrite list (request side) These fields are copied with a plain assignment. The merger never checks whether the incoming value is null, blank or defaulted: - `port`, `baseUri`, `basePath` - `authenticationScheme` - `requestBody` - `urlEncodingEnabled`, `allowContentType`, `addCsrfFilter` - `proxySpecification` - `method` and `path` - the unnamed, positional path-parameter tuples The last one is the sharp edge inside a sharp edge: **named** path parameters accumulate, but the unnamed positional values are replaced wholesale. ## The accumulate list (request side) These are merged with `putAll` or `addAll`, so entries from both sides reach the wire: - request parameters, query parameters and form parameters - named path parameters - multiparts - cookies - headers - filters Two details are worth carrying. Filters are de-duplicated by object identity — the same `Filter` instance present on both sides is added once — and auth filters are special-cased: if both specifications carry one, the existing auth filters are dropped first, and an incoming `ExplicitNoAuthScheme` (what `auth().none()` installs) removes them all. Headers are merged **last**, after the configuration merge, precisely because how same-named headers combine is governed by `HeaderConfig` — a separate topic, but the ordering inside the merger exists to make it work. ## The response side `SpecificationMerger` has a second overload for response specifications, and it splits the same way: | Overwritten | Accumulated | |---|---| | `contentType` | `bodyMatchers` | | `bodyRootPath` | `cookieAssertions` | | `expectedStatusCode` | `headerAssertions` | | `expectedStatusLine` | registered custom parsers | | `expectedResponseTime` | | | `responseLogDetail` and the default parser | | So merging a specification that expects `201` into one that expected `200` leaves you with a single expectation of `201`, while merging one that checks `bookingRef` into one that checks `status` leaves you asserting both. ## Configuration is merged by object, not by field `RestAssuredConfig` is handled separately from everything above. The merger compares `isUserConfigured()` on each side and then, config object by config object, keeps the incoming one if *it* reports being user-configured and the existing one otherwise. It is all-or-nothing per config object rather than per setting — a distinction that belongs to the configuration topic, but which is worth knowing exists so you do not look for it in the field lists. ## A worked example on the booking API ```java RequestSpecification trekSpec = new RequestSpecBuilder() .setBasePath("/v2") .addQueryParam("region", "andes") .build(); given().basePath("/v1") .queryParam("difficulty", "alpine") .spec(trekSpec) .when().get("/treks"); ``` The call goes to `/v2/treks`, not `/v1/treks` and not `/v1/v2/treks`, because `basePath` is on the overwrite list. It carries **both** `region=andes` and `difficulty=alpine`, because query parameters are on the accumulate list. One chain, two rules, and the request log will show you exactly that. ## How to remember which is which The split follows a simple intuition once you see it: **a field that can only hold one value is assigned; a field that is a map or a list is combined.** There is exactly one place where the intuition needs a footnote — unnamed path parameters are a list and are still replaced. - If you can only have one of it — a host, a port, a body, a verb — it is overwritten. - If you can have many of them — headers, cookies, parameters, filters, matchers — they add up. - Configuration is neither: it is swapped per config object by its user-configured flag. ## Why it matters in practice - A shared spec that sets a body will silently discard the body a specific test set beforehand. - A shared spec that adds a filter never removes the filter you already registered, so logging filters stack up and a request can be printed twice. - Merging the same response specification into two others accumulates body matchers each time, which is what you want for reusable expectation blocks and surprising if you expected replacement. Once the two lists are in your head, reading any chain that contains `spec(...)` becomes mechanical.
- Named path parameters accumulate but the unnamed positional ones do not. Why does that asymmetry exist?Named parameters are a map, so `putAll` combines them without ambiguity. The unnamed values are an ordered tuple whose meaning depends entirely on position, and there is no sensible way to interleave two orderings — so `SpecificationMerger` assigns `unnamedPathParamsTuples` from the incoming spec instead of merging it.
- If filters accumulate, what stops the same logging filter being registered twice?`mergeFilters` adds only the filters the receiving specification does not already contain, so the same `Filter` instance present on both sides appears once. Two separately constructed instances of the same class are different objects and both survive, which is how duplicate request logs usually appear.
saying these in an interview costs you the question
- Saying basePath from a spec is appended to the current one
- Expecting every field to merge because the method is called merge
- Assuming a second request body is appended to the first
- Thinking response body matchers replace earlier ones
- Believing configuration is merged setting by setting