skip to content

Builder Classes

How a reusable specification is assembled: the two builder classes, what their constructors capture at that moment, and how one built spec folds into another. The add-versus-set split is probed.

part ofREST Assuredoverview, primer and where to startread it →
on this pageshow

explore

questions

7

In REST Assured, how do you build one RequestSpecification that every test in a suite reuses?

level: juniorimportance: must knowfreq 70%

answer

  1. builder assembles, build() hands back
  2. setBaseUri, setBasePath, addHeader
  3. one static field, every case starts there
  4. RequestSpecification, not the builder

basics

~20 s

Chain RequestSpecBuilder setters such as setBaseUri, setBasePath, addHeader and setContentType, then call build() once to obtain a RequestSpecification. Hold that object in a static field so every case starts from it instead of repeating the depot address and headers.

solid answer

~50 s

`RequestSpecBuilder` is REST Assured's fluent assembly surface for shared request setup. You construct one, call its setters — `setBaseUri("https://api.milkround.test")`, `setBasePath("/v2")`, `setContentType(ContentType.JSON)`, `setAccept(ContentType.JSON)`, `addHeader("X-Depot", "kirkstall")`, `addCookie("depotSession", id)`, `setAuth(...)`, `setConfig(...)`, `addFilter(new RequestLoggingFilter())` — and finish with `build()`, which hands back a `RequestSpecification`. That returned object is the template: hold it in one place, typically a `static final` field on a test-support class, and start every case from it rather than restating the depot address and the standing headers. Keep the template to what genuinely every call needs; a path segment, a case body or a one-off parameter belongs in the test itself. `addRequestSpecification(other)` folds an already-built specification into the one you are assembling, which is how a suite layers an environment template over a common one. The thing you circulate is the built `RequestSpecification`, never the builder, and `build()` validates nothing: not the host, not the credentials, not the base path.

code

java · 15 lines
java
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.filter.log.RequestLoggingFilter;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;

RequestSpecification milkRound = new RequestSpecBuilder()
        .setBaseUri("https://api.milkround.test")
        .setBasePath("/v2")
        .setContentType(ContentType.JSON)
        .setAccept(ContentType.JSON)
        .addHeader("X-Depot", "kirkstall")
        .addQueryParam("roundDate", "2026-04-18")
        .addCookie("depotSession", "d41f9c")
        .addFilter(new RequestLoggingFilter())
        .build();

go deeper

for a junior

Be able to write the chain from memory: construct RequestSpecBuilder, call setBaseUri, setBasePath, setContentType and addHeader, then build(). Know that build() returns a RequestSpecification and that the builder is not the thing you reuse.

for a middle

Explain why each value sits in the template rather than the test, and name the composition method addRequestSpecification. Be ready to say what the builder stores versus what the authentication and configuration surfaces decide.

for a senior

Show judgment about template size: what every case needs versus what one case would have to undo. Be ready to describe how a suite you inherited was restructured around one or two templates and what that fixed in practice.

for a principal

Own the tradeoff between one maximal template that is easy to find and several narrow ones that compose. Argue it in terms of what changes together, how a new joiner discovers the setup, and what a wrong shared default costs across a whole suite.

## Why a shared request specification exists A suite against a milk-round delivery API opens nearly every case the same way: the depot host, the `/v2` prefix, `Content-Type: application/json`, an `X-Depot` routing header naming which depot the caller speaks for, a `depotSession` cookie, and a logging filter so a CI failure prints something a human can read. Written out per test, that preamble gets copied two hundred times and edited two hundred times the day the depot moves to a new host. A **request specification** — `io.restassured.specification.RequestSpecification` — is REST Assured's object for holding that preamble once. It is an ordinary Java object: you can store it in a field, pass it around, and start calls from it. ## RequestSpecBuilder is the assembly surface `io.restassured.builder.RequestSpecBuilder` is how you assemble one. Every method returns the builder, so a template is a single chain ending in `build()`, and `build()` hands back a `RequestSpecification`. The setters group into a handful of jobs: - **Where the call lands** — `setBaseUri(String)` or `setBaseUri(URI)`, `setBasePath(String)`, `setPort(int)`. - **What travels with every call** — `addHeader`/`addHeaders`, `addCookie`/`addCookies`, `addParam`, `addQueryParam`, `addFormParam`, `addPathParam`. - **How the payload is described** — `setContentType`, `setAccept`, `setBody`, `noContentType()`. - **Who the caller is** — `setAuth(AuthenticationScheme)`, `setSessionId(String)`. - **Transport and instrumentation** — `addFilter`/`addFilters`, `setConfig(RestAssuredConfig)`, `setProxy(...)`, `setKeyStore`/`setTrustStore`, `setRelaxedHTTPSValidation()`. - **Composition** — `addRequestSpecification(RequestSpecification)`, which folds an already-built specification into the one being assembled. Which credential object goes into `setAuth`, and which `RestAssuredConfig` instance you hand to `setConfig`, are decisions the authentication and configuration surfaces own. The builder only stores what you give it. ## What belongs in the template and what does not The judgment call is not *how* to build a specification but *how much* to put in one. A template carrying too much turns every test into a special case that has to undo something. | Put in the shared specification | Leave in the individual test | |---|---| | base URI, base path, port | the path `/rounds/{roundId}/drops` and the verb | | `Accept` and a default `Content-Type` | the request body for this case | | routing and correlation headers | a parameter only this case sends | | the auth scheme nearly all cases use | a case that must call unauthenticated | | logging and timing filters | every assertion | The rule of thumb: if a single test would have to override it, it is a candidate for staying out of the template. ## Sharing one template across a suite 1. **Build it once.** A `static final RequestSpecification` on a small test-support class is the usual home, constructed from whatever your environment lookup yields rather than from a literal. 2. **Start every call from it**, then add only what this case needs on top. 3. **Layer rather than duplicate.** A common template plus a narrower one composed through `addRequestSpecification` beats two near-identical builders that drift apart over a year. 4. **Name templates for the setup they carry**, not for the tests that happen to use them — `authenticatedDepotSpec` ages far better than `spec2`. ## Three things the template is not - It is not **validated** at `build()`. Nothing checks that the host resolves, that the credentials work, or that `/v2` exists; a typo in `setBasePath` surfaces as a 404 in every test at once. - It is not **read lazily**. `new RequestSpecBuilder()` copies the `RestAssured` static defaults in its constructor, so anything assigned to those statics after the builder exists is missing from it. - It is not **the response half**. Expected status codes and body shapes are assembled by a separate builder and attached on the validation side of the chain. ## Reading a failure through the template When one case fails and the rest pass, the template is almost certainly innocent — look at what that case added. When every case fails identically, the template is the first place to look, and the cheapest check is to read it top to bottom against what the service actually expects: host, prefix, content type, routing header, auth scheme. Because the specification is one object in one file, that read takes a minute. The same information smeared across two hundred request chains does not, and that difference — not keystrokes saved — is the real argument for building the template at all.

  • Where in a suite should the built RequestSpecification live?
    In one place that every case can reach — typically a `static final` field or a factory method on a small test-support class, constructed from your environment lookup rather than from a literal host. The point is a single edit site: when the depot moves, one line changes instead of every test.
  • Which RequestSpecBuilder method folds an already-built specification into the one you are assembling?
    `addRequestSpecification(RequestSpecification)`. It takes a specification you built earlier and merges it into the builder's own, which lets a suite keep one narrow base template and compose it into several fuller ones instead of maintaining near-identical builders that drift apart.
  • How much setup should the shared template carry?
    Only what genuinely every call needs: host, base path, port, `Accept` and default `Content-Type`, routing headers, the auth scheme almost all cases use, and instrumentation filters. If a single test would have to override it, it is a candidate for staying out — an over-full template turns tests into exercises in undoing setup.

saying these in an interview costs you the question

  • Thinks the RequestSpecBuilder itself is what you pass to a request
  • Believes a specification can only hold a base URI and headers
  • Copies the base URI into every test and calls that reuse
  • Assumes build() validates the host, credentials or base path
  • Puts case-specific bodies and parameters into the shared template
open as a page

In REST Assured's RequestSpecBuilder, what is the difference between its addX and setX methods?

level: juniorimportance: should knowfreq 45%

basics

~20 s

RequestSpecBuilder's setX methods fill a single slot, so a second call replaces the first: setBaseUri, setBasePath, setPort, setContentType, setAuth, setBody, setConfig. Its addX methods append to a collection, so repeated calls accumulate: addHeader, addCookie, addQueryParam, addFilter, addMultiPart.

open as a page

In REST Assured, what does a ResponseSpecBuilder let you expect on every response?

level: juniorimportance: should knowfreq 48%

basics

~20 s

ResponseSpecBuilder collects the checks every response must pass — status code, status line, content type, headers, cookies, response time and body paths — and build() returns one ResponseSpecification. It describes expectations only; a real response satisfies them later.

open as a page

In REST Assured, what does RequestSpecBuilder.build() return, and what happens when you call it twice?

level: middleimportance: should knowfreq 36%

basics

~20 s

build() returns the RequestSpecification the builder has been mutating all along, the same object every call, never a copy. Two specs built from one builder are one aliased spec, and later setter calls change a template already handed out.

open as a page

In REST Assured's ResponseSpecBuilder, which expectations stack and which replace an earlier call?

level: middleimportance: should knowfreq 36%

basics

~20 s

A REST Assured ResponseSpecBuilder accumulates body, header and cookie expectations, so repeated calls all run. Status code, status line, content type, response time and log detail are single fields: a second call silently replaces the first. There is no warning.

open as a page

A REST Assured RequestSpecBuilder ignores the RestAssured.baseURI your setup sets - how do you diagnose and fix it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

new RequestSpecBuilder() copies the RestAssured statics - baseURI, port, basePath, authentication, filters, config, proxy and urlEncodingEnabled - in its constructor, so a builder created before your setup ran keeps the old values. Construct it afterwards, or call setBaseUri directly.

open as a page

A REST Assured ResponseSpecBuilder loses its expectStatusCode(200) after addResponseSpecification(...) — why?

level: seniorimportance: should knowfreq 27%

basics

~20 s

ResponseSpecBuilder.addResponseSpecification runs the response-side merge, which assigns the incoming spec's scalar fields unconditionally. Because that spec never set a status code, its null value is written over your 200. Only body, header and cookie assertions accumulate.

open as a page