skip to content

In REST Assured, how do you read a header or base URI back off a built RequestSpecification?

level: middleimportance: must knowfreq 42%

answer

  1. a spec can be read, not only sent
  2. one static helper, no HTTP involved
  3. query(spec) returns a read-only view
  4. getDefinedFilters, never getFilters

basics

~20 s

REST Assured reads a built request specification back through SpecificationQuerier.query(spec), which returns a QueryableRequestSpecification. That read-only view exposes getters such as getBaseUri, getBasePath, getHeaders, getCookies and getDefinedFilters. No request is sent, so it runs in a plain unit test.

solid answer

~50 s

`io.restassured.specification.SpecificationQuerier.query(spec)` hands you a `QueryableRequestSpecification` — a read-only window onto what a `RequestSpecification` holds. It works because everything REST Assured builds (`new RequestSpecBuilder().build()`, `given()`, `RestAssured.requestSpecification`) is a `RequestSpecificationImpl`, which already implements that interface; `query` is a guarded cast that throws `IllegalArgumentException` for anything else. From there you read `getBaseUri()`, `getBasePath()`, `getPort()`, `getContentType()`, `getHeaders()`, `getCookies()`, `getBody()`, `getRequestParams()`, `getQueryParams()`, `getFormParams()`, `getNamedPathParams()`, `getConfig()` and `getDefinedFilters()`. `getHeaders()` returns REST Assured's `Headers` type, so you finish with `getValue("X-Greenhouse-Site")`. Because nothing is sent, a shared greenhouse-climate specification can be asserted in an ordinary test with no server and no port, which makes a broken specification factory fail one fast test instead of the whole suite. Two caveats: the view is live rather than a snapshot; and it reports what you *defined* — the default `Accept: */*` header and REST Assured's own internal filters appear only when the call goes out.

code

java · 20 lines
java
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.*;

RequestSpecification climateSpec = new RequestSpecBuilder()
        .setBaseUri("https://climate.greenhouse.test")
        .setBasePath("/api/v2")
        .addHeader("X-Greenhouse-Site", "delta-4")
        .addQueryParam("granularity", "hourly")
        .setContentType("application/json")
        .build();

QueryableRequestSpecification q = SpecificationQuerier.query(climateSpec);

q.getBaseUri();                               // "https://climate.greenhouse.test"
q.getBasePath();                              // "/api/v2"
q.getHeaders().getValue("X-Greenhouse-Site"); // "delta-4"
q.getQueryParams().get("granularity");        // "hourly"
q.getContentType();                           // "application/json"
q.getHeaders().hasHeaderWithName("Accept");   // false - added at send time
q.getDefinedFilters();                        // unmodifiable, no SendRequestFilter yet

go deeper

for a junior

Know that REST Assured can read a RequestSpecification back with SpecificationQuerier.query(spec), and that nothing is sent when you do. Be able to name two getters, such as getBaseUri and getHeaders.

for a middle

Explain why the call is a guarded cast: everything RequestSpecBuilder and given() produce already implements QueryableRequestSpecification. Be ready to say which values are missing because REST Assured only fills them at send time.

for a senior

Show the production use: a fast test per shared specification factory that asserts base URI, headers and filters with no server running, so a broken spec fails one cheap test instead of reddening the whole suite.

for a principal

Own the convention. Decide whether shared specifications are built by factories with querier-backed guard tests, and weigh that cost against discovering the same defects only through long end-to-end runs.

## Why a specification needs a read side A REST Assured `RequestSpecification` looks write-only. You call `setBaseUri`, `addHeader` and `addQueryParam` on a `RequestSpecBuilder`, call `build()`, and from then on the only evidence of what is inside it is a request that actually went out over the network. For the specification every test in a greenhouse climate suite starts from, that is a slow and indirect feedback loop: `climateSpec` can carry the wrong site header for a hundred tests, and the cheapest way to find out is a round trip against a running service. `io.restassured.specification.SpecificationQuerier` closes it. It is one static method: ```java QueryableRequestSpecification queryable = SpecificationQuerier.query(spec); ``` Nothing is sent, no port is bound, nothing is stubbed. It is an ordinary object read. ## How query(...) works It is neither reflection nor a copy. Everything REST Assured itself produces — `new RequestSpecBuilder().build()`, `RestAssured.given()`, the static `RestAssured.requestSpecification` — is a `RequestSpecificationImpl`, and that class implements `FilterableRequestSpecification`, which extends `QueryableRequestSpecification`. `query(...)` performs an `instanceof` check and a cast; hand it a specification you implemented yourself and it throws `IllegalArgumentException`. Two consequences follow: - The returned view is **live**, not a snapshot — it is the same object seen through a narrower interface, so values change under you if you keep calling setters. Query after the last mutation you care about. - Inside a filter you never need `SpecificationQuerier` at all: the `FilterableRequestSpecification` handed to you already *is* a `QueryableRequestSpecification`, and it adds the mutators (`removeHeader`, `replaceHeader`, `removeQueryParam`, `path`) that the read-only interface lacks. ## What you can read | What you want to know | Getters | |---|---| | Where the call lands | `getBaseUri()`, `getBasePath()`, `getPort()`, `getURI()`, `getMethod()`, `getDerivedPath()`, `getUserDefinedPath()` | | What travels with it | `getHeaders()`, `getCookies()`, `getContentType()`, `getBody()`, `getMultiPartParams()` | | Parameters | `getRequestParams()`, `getQueryParams()`, `getFormParams()` | | Path placeholders | `getPathParams()`, `getNamedPathParams()`, `getUnnamedPathParams()`, `getUnnamedPathParamValues()`, `getPathParamPlaceholders()`, `getUndefinedPathParamPlaceholders()` | | Machinery | `getDefinedFilters()`, `getAuthenticationScheme()`, `getProxySpecification()`, `getConfig()`, `getHttpClient()` | Two details worth memorising: - The filter getter is **`getDefinedFilters()`**. There is no `getFilters()` on this interface, and guessing that name is the single commonest way to fail to compile against it. - `getHeaders()` returns REST Assured's own `Headers` type, so you finish the call with `getValue("X-Greenhouse-Site")`, `getValues(...)` or `hasHeaderWithName(...)`; `getCookies()` returns `Cookies` with the same shape. The parameter maps and the filter list come back through `Collections.unmodifiableMap` and `Collections.unmodifiableList`. Calling `put` or `add` on one throws `UnsupportedOperationException`; changing a specification goes through the specification's own API instead. ## Defined, not sent The most useful thing to internalise is that the querier reports the **specification**, not the wire. Several things REST Assured puts on a request are decided when the call goes out, so they are absent from a spec you query beforehand: - The default `Accept: */*` header, which is only added when you never set one yourself. - The `Content-Type` derived from form parameters or a multipart body — `getContentType()` simply reads back the `Content-Type` header that `contentType(...)` wrote. - `getMethod()`, which is `null` until an HTTP verb has been issued, and with it the exact shape of `getURI()`. - REST Assured's internal filters: `SendRequestFilter` (appended last, and the thing that actually sends), `TimingFilter`, the form-auth and CSRF filters, and the logging filters that `enableLoggingOfRequestAndResponseIfValidationFails()` installs. One thing resolves earlier than people expect: `body(pojo)` serialises immediately, so `getBody()` hands back the serialised `String`, not your object. ## Where it pays off 1. **Guard a shared specification.** One test per factory method that queries the built spec and asserts base URI, base path, content type and the site header. It runs in milliseconds against no server and catches the copy-paste that would otherwise redden every test in the suite. 2. **Explain a precedence surprise.** When a per-test override and a shared specification disagree, querying at the exact point of the chain shows which value survived, with no log reading and no round trip. 3. **Document the harness.** A test that prints or asserts `getDefinedFilters()` and `getConfig()` is a living description of what the suite actually attaches to every call. ## The edge of the mechanism `SpecificationQuerier.query(...)` accepts a `RequestSpecification` and nothing else. There is no response-side querier: a `ResponseSpecification` is readable only through the `FilterableResponseSpecification` a filter receives, and values from a real response come off the response object, never off a specification.

  • What does SpecificationQuerier.query() do when the specification is not one REST Assured built?
    It throws `IllegalArgumentException`. The method is a guarded cast: it checks `instanceof QueryableRequestSpecification`, casts when the check passes, and rejects everything else with a message naming the interface. Every specification REST Assured itself creates passes, so in practice you only see this if you wrote your own `RequestSpecification` wrapper.
  • Is the object returned by query() a snapshot of the specification at that moment?
    No. It is the same underlying object exposed through a read-only interface, so setters called afterwards are visible through it. If you need a stable record, copy the values you care about into locals at the moment you query, rather than holding the `QueryableRequestSpecification` and reading it later.
  • Can you change a specification through the querier?
    No. The maps and the filter list come back unmodifiable, and the interface declares only getters. To change a specification you use its own API — `noFilters()`, `noFiltersOfType(...)`, or the `remove*` and `replace*` methods that `FilterableRequestSpecification` adds when a filter is holding the request.

The specification is not opened and copied; it is put behind a window. You are looking at the same object through an interface that only lets you read it.

saying these in an interview costs you the question

  • Says you must send a request to see what a spec contains
  • Calls the filter getter getFilters instead of getDefinedFilters
  • Thinks SpecificationQuerier also reads a ResponseSpecification
  • Expects the queried headers to include the default Accept header
  • Tries to mutate the specification through the maps the querier returns
  • Assumes the returned view is an immutable snapshot