skip to content

In REST Assured, what does calling queryParam() twice with the same name send, and how do you change it?

level: middleimportance: must knowfreq 57%

answer

  1. nothing is silently dropped
  2. both values reach the wire
  3. ParamConfig owns the collision rule
  4. UpdateStrategy has only two constants
  5. REPLACE makes the last call win

basics

~10 s

Both values are sent. REST Assured merges same-name parameters by default, producing zone=north&zone=south. To make the last call win, set ParamConfig's queryParamsUpdateStrategy to UpdateStrategy.REPLACE, or use replaceAllParameters().

solid answer

~30 s

Nothing is overwritten: `ParamConfig` defaults query, form and request parameters to `UpdateStrategy.MERGE`, so `queryParam("zone", "north").queryParam("zone", "south")` sends `GET /v1/routes?zone=north&zone=south`. That is the right default for a genuinely multi-valued filter and the wrong one when a test is trying to override a value a shared specification already set. To get last-call-wins, build a config: `given().config(config().paramConfig(paramConfig().queryParamsUpdateStrategy(REPLACE)))`. `formParamsUpdateStrategy(...)` and `requestParamsUpdateStrategy(...)` do the same for the other two kinds, and `replaceAllParameters()` flips them all. If you actually want two values, say it in one call — `queryParam("zone", "north", "south")` — so the intent is visible without knowing the default.

code

java · 28 lines
java
import static io.restassured.RestAssured.config;
import static io.restassured.RestAssured.given;
import static io.restassured.config.ParamConfig.UpdateStrategy.REPLACE;
import static io.restassured.config.ParamConfig.paramConfig;

class DispatchZoneFilterTest {

    void bothZonesAreSent() {
        given()
            .queryParam("zone", "north")
            .queryParam("zone", "south")
        .when()
            .get("/v1/routes")            // GET /v1/routes?zone=north&zone=south
        .then()
            .statusCode(200);
    }

    void lastZoneWins() {
        given()
            .config(config().paramConfig(paramConfig().queryParamsUpdateStrategy(REPLACE)))
            .queryParam("zone", "north")
            .queryParam("zone", "south")
        .when()
            .get("/v1/routes")            // GET /v1/routes?zone=south
        .then()
            .statusCode(200);
    }
}

go deeper

for a junior

Remember that repeating a parameter name adds a value rather than replacing it, so the URL carries both. That alone explains most surprising query strings you will meet.

for a middle

Explain the merge default, name ParamConfig and UpdateStrategy.MERGE versus REPLACE, and show how to build the config per request. Mention the varargs overload as the clearer way to send two values.

for a senior

Diagnose it in a suite: a base specification and a test both setting zone produce a two-value filter and a confusing failure. Argue for scoping the strategy change instead of flipping it globally.

for a principal

Decide the convention. Either the shared specification owns parameters and tests never re-set them, or override semantics are configured and documented once - drifting between the two is what makes suites unpredictable.

## The default is merge, and that is a deliberate choice `RestAssuredConfig` holds eighteen configuration objects, and the one that governs this behaviour is `ParamConfig`. Its `UpdateStrategy` enum has exactly **two** constants, `MERGE` and `REPLACE`, and query, form and request parameters all default to `MERGE`. So this pair of calls against a snowplough dispatch API: ```java given().queryParam("zone", "north").queryParam("zone", "south").when().get("/v1/routes"); ``` sends `GET /v1/routes?zone=north&zone=south` — two values under one name, in call order. Nothing is lost and nothing warns you. Under the hood the parameter updater sees the name already present, turns the stored value into a list and appends; with `REPLACE` it simply overwrites the entry. That default is right for the common case — repeated names are how HTTP expresses a multi-valued filter — and wrong for the case that actually bites in a suite, which is a base specification that already set the parameter you are now setting. ## Turning it into last-call-wins `ParamConfig` is immutable and built fluently, so you construct the variant you want and hand it to the config: ```java given() .config(config().paramConfig(paramConfig().queryParamsUpdateStrategy(REPLACE))) .queryParam("zone", "north") .queryParam("zone", "south") .when() .get("/v1/routes"); ``` Now the request is `GET /v1/routes?zone=south`. The knobs available are: - `queryParamsUpdateStrategy(UpdateStrategy)` — governs `queryParam(...)` and `queryParams(...)`. - `formParamsUpdateStrategy(UpdateStrategy)` — governs `formParam(...)` and `formParams(...)`. - `requestParamsUpdateStrategy(UpdateStrategy)` — governs the verb-driven `param(...)` and `params(...)`. - `replaceAllParameters()` — the only shortcut that sets every parameter type to `REPLACE`. - `mergeAllParameters()` — sets the three configurable types to `MERGE`; note that it does **not** change path parameters, which are `REPLACE` and have no setter at all. You can apply the config per request with `given().config(...)`, on a shared `RequestSpecBuilder.setConfig(...)`, or globally by assigning `RestAssured.config`. Prefer the narrowest scope that fixes the problem: flipping the strategy globally changes how every existing multi-value filter in the suite behaves, and multi-value filters are the case the default was designed for. ## Deliberate multi-value, without touching the config If two values is what you *want*, say so in one call rather than relying on the merge default: 1. Varargs: `queryParam("zone", "north", "south")` records both values under one name. 2. A collection: `queryParam("zone", List.of("north", "south"))` does the same from a list. 3. A map: `queryParams(Map.of("zone", "north", "shift", "night"))` sets several names at once, each subject to the same update strategy. Written that way the intent is visible at the call site, and a reader does not have to know the merge default to predict the URL. ## The trap this question is really about The painful version is not two literal calls next to each other. It is: - a shared `RequestSpecification` that already carries `queryParam("zone", "north")` for the default depot, plus - a test that adds `queryParam("zone", "south")` because it wants the southern depot. The dispatch service receives `zone=north&zone=south`, interprets it as "both zones" or rejects it as ambiguous, and the test fails in a way that points at the endpoint rather than at the spec. The same shape appears when specifications are merged: attaching a spec accumulates parameters rather than overwriting them, so the merge default and specification merging compound each other. ## How to decide | situation | what to do | |---|---| | You genuinely want a multi-valued filter | Keep `MERGE`, and pass both values in one call | | A test must override a shared spec's parameter | `REPLACE` for that parameter type, scoped to the request | | A suite where overriding is the norm | `replaceAllParameters()` on the shared config, documented | | You want to remove rather than override | Use the filter-side removal API rather than re-setting it | The last row matters: changing the strategy is not the only lever. A `FilterableRequestSpecification` inside a filter can remove a parameter outright, which is the honest move when a request must go out with no `zone` at all. ## What to say in an interview State the default in one sentence — same-name parameters merge, so both values are sent — then show you know where the switch is: `ParamConfig`, `UpdateStrategy.REPLACE`, and the per-type setters. Finish with the judgment call: scope the change to the request or the spec that needs it, because the global flip silently rewrites how every multi-value filter in the suite behaves.

  • Would you set replaceAllParameters() globally on a large REST Assured suite?
    Rarely. Assigning it to `RestAssured.config` changes every existing multi-value filter in the suite from two values to one, and those tests fail for a reason that has nothing to do with what you were fixing. Scope the change to the request or the shared specification that needs override semantics, and document why.
  • How do you send a genuinely multi-valued query parameter in REST Assured without relying on the merge default?
    Pass the values in one call: `queryParam("zone", "north", "south")` uses the varargs overload, and there is a `Collection` overload for a list you already hold. Both record several values under one name, so the URL is predictable whatever the configured update strategy happens to be.

saying these in an interview costs you the question

  • Says the second queryParam() call silently overwrites the first
  • Thinks REST Assured throws on a duplicate parameter name
  • Cannot name ParamConfig as the place the rule lives
  • Believes only the last value reaches the server
  • Flips replaceAllParameters() globally without weighing multi-value filters