In REST Assured, why do getRequestParams, getQueryParams and getFormParams return different maps?
answer
- one getter per setter, nothing migrates
- param is untyped until a verb exists
- an empty map usually means wrong map
- getURI shows the resolved request line
basics
~20 sREST Assured stores parameters in three separate maps, and the querier mirrors them one for one: param feeds getRequestParams, queryParam feeds getQueryParams, formParam feeds getFormParams. A specification never resolves an untyped param, so reading the wrong map returns nothing.
solid answer
~50 sThe getters mirror the setters exactly. `param(...)` and `RequestSpecBuilder.addParam(...)` land in `getRequestParams()`; `queryParam(...)`/`addQueryParam(...)` in `getQueryParams()`; `formParam(...)`/`addFormParam(...)` in `getFormParams()`. Nothing migrates between them, so a greenhouse readings spec built with `addParam("sensor", "co2")` returns an empty `getQueryParams()` — the commonest surprise on this API. The reason is that `param` is untyped: whether it becomes a query parameter or a form field depends on the HTTP verb, and a specification has none yet, since `getMethod()` reads `null` until a verb is issued. If you want the resolved picture rather than the intent, read `getURI()`, which folds the request parameters into the query string for anything that is not a POST. All three maps come back unmodifiable; `removeParam`, `removeQueryParam` and `removeFormParam` on the filterable specification are how you edit them. The habit worth forming is to check all three, or `getURI()`, before concluding that a parameter went missing.
code
java · 15 linesRequestSpecification readings = new RequestSpecBuilder()
.setBaseUri("https://climate.greenhouse.test")
.setBasePath("/api/v2/readings")
.addParam("sensor", "co2") // untyped
.addQueryParam("granularity", "hourly") // explicitly a query param
.build();
QueryableRequestSpecification q = SpecificationQuerier.query(readings);
q.getRequestParams().get("sensor"); // "co2"
q.getQueryParams().get("sensor"); // null - a different map
q.getQueryParams().get("granularity"); // "hourly"
q.getFormParams().isEmpty(); // true
q.getMethod(); // null - no verb yet
q.getURI(); // .../api/v2/readings?sensor=co2&granularity=hourlygo deeper
Remember that REST Assured keeps request, query and form parameters apart, and that you read each one back with the getter matching the setter you used.
Explain why an untyped param cannot be resolved on a specification: the destination depends on the HTTP verb, and getMethod is null until one is issued. Name getURI as the resolved view.
When a guard test or a merge appears to lose a parameter, check all three maps and getURI before doubting the library. Push the team towards explicit queryParam and formParam so intent survives into the read-back.
Set the convention. Deciding that shared specifications only ever use explicit query and form parameters removes a whole class of ambiguity from both the request and the tests that inspect it.
## Three setters, three maps, three getters REST Assured does not keep one parameter bag. A `RequestSpecification` holds separate internal maps, and `QueryableRequestSpecification` exposes each one under the getter that matches the setter which filled it: | You called | The value lands in | You read it back with | |---|---|---| | `param(...)` / `params(...)` / `RequestSpecBuilder.addParam(...)` | the request-parameter map | `getRequestParams()` | | `queryParam(...)` / `addQueryParam(...)` | the query-parameter map | `getQueryParams()` | | `formParam(...)` / `addFormParam(...)` | the form-parameter map | `getFormParams()` | | `pathParam(...)` / `addPathParam(...)` | the named path-parameter map | `getNamedPathParams()` | The mapping is one for one, and it is total: nothing ever migrates between the maps while the specification is alive. Read the wrong one and you get an empty result and a confusing five minutes. ## Why the specification refuses to resolve `param` `param(...)` is deliberately untyped. Where it ends up depends on the HTTP verb — a request parameter becomes part of the query string on most methods and part of the form body on a POST — and a specification does not know the verb. `getMethod()` reads `null` until an HTTP verb has been issued. So the specification stores the value in the request-parameter map and leaves the decision to dispatch time. That is why: - `new RequestSpecBuilder().addParam("sensor", "co2").build()` gives an **empty** `getQueryParams()`. - The same specification gives `"co2"` from `getRequestParams().get("sensor")`. - Nothing you can query before the verb will tell you whether that parameter ends up in the URL or in a form body, because the library has not decided yet either. The practical rule: if you want to know what was *set*, read the map that matches the setter. Use the explicit `queryParam` and `formParam` on the setting side whenever the destination matters, and then the read-back is unambiguous too. ## The getter that does show a resolved request line There is one exception, and it is the useful one. `getURI()` assembles the request line as it stands: it merges base URI, base path, path and the parameters, folding the request parameters into the query string for anything that is not a POST, adding the query parameters, and adding form parameters when the method is GET. So for the greenhouse readings specification: - `getRequestParams()` answers *what did I set untyped?* - `getQueryParams()` answers *what did I set explicitly as a query parameter?* - `getURI()` answers *what does the URL look like right now?* Use the maps for assertions about intent, and `getURI()` for an assertion about the resulting URL. Note that `getURI()` is evaluated against whatever method the specification currently carries, so on a specification that has never been given a verb it behaves as a non-POST. ## Reading the wrong map — the symptoms The failure mode is quiet, which is what makes it worth knowing: - A guard test asserts `getQueryParams()` on a specification built with `addParam(...)` and finds nothing, so the author concludes the querier is broken. - A parameter appears in the request log but not in the map the test reads, so the test is weakened to a log assertion. - A merged specification seems to lose a parameter, when in fact both specifications wrote to different maps and both values are present under different getters. Checking all three maps, or checking `getURI()`, resolves every one of these in seconds. ## Changing what you find All three maps come back through `Collections.unmodifiableMap`, so mutating them throws `UnsupportedOperationException`. Adding goes through the specification's own setters. Removing goes through `FilterableRequestSpecification`, which extends the queryable interface and adds `removeParam`, `removeQueryParam` and `removeFormParam` — that is the pairing to remember: the queryable half reads, the filterable half edits, and a filter receives an object that does both. ## What a good guard test asserts Because all of this is object state, the check is cheap enough to keep permanently. For the greenhouse readings specification, one small test is enough: 1. Assert `getQueryParams().get("granularity")` equals the value the suite depends on, so a change to the factory is caught immediately. 2. Assert `getRequestParams()` is empty if the team's convention is to use only the explicit setters — that turns the convention into something enforced rather than merely agreed. 3. Assert `getURI()` contains the parameters you expect in the URL, which is the closest thing to a wire-level assertion you can make without a server. The last one is worth the extra line. Intent and result are different questions, and a specification that carries the right values in the wrong map still produces a URL you did not intend the moment a verb turns up and resolves them.
- A parameter shows up in the request log but not in getQueryParams(). What happened?It was almost certainly set with `param(...)` or `addParam(...)`, so it lives in the request-parameter map and reads back from `getRequestParams()`. REST Assured only decides that an untyped parameter belongs in the query string when it dispatches the request, which is why the log and the map disagree.
- How do you remove a parameter you find while inspecting a specification?Not through the returned map — it is unmodifiable and throws `UnsupportedOperationException`. Use the specification itself: `removeParam`, `removeQueryParam` and `removeFormParam` are declared on `FilterableRequestSpecification`, which extends the queryable interface, so a filter holding the request can both read and edit.
saying these in an interview costs you the question
- Expects addParam to show up in getQueryParams
- Thinks the querier resolves an untyped param by guessing the verb
- Concludes the querier is broken when a map comes back empty
- Tries to put a value into the map the querier returned
- Believes a merge lost a parameter that is simply in another map