Headers are how a client and server communicate everything except the payload. Auth credentials, content negotiation, request tracing, caching — all live in headers. The API Testing Masterclass lesson on HTTP methods, status codes, and headers covered the conceptual side; this lesson is the Rest Assured DSL for setting them, plus the half-dozen headers every API tester ends up reaching for. Get this right and your auth, content negotiation, and tracing all become one-liners.
Setting one header at a time
The simplest form — header(name, value):
given()
.header("Accept", "application/json")
.header("Authorization", "Bearer " + token)
.header("X-Request-Id", UUID.randomUUID().toString())
.when()
.get("/users")
.then()
.statusCode(200);Each call adds one header. You can call .header(...) as many times as you want — order doesn't matter for HTTP, and Rest Assured preserves them all.
Setting many headers at once
When you have a Map or an existing Headers object:
import io.restassured.http.Header;
import io.restassured.http.Headers;
Headers headers = new Headers(
new Header("Accept", "application/json"),
new Header("Authorization", "Bearer " + token),
new Header("X-Custom-Header", "custom-value")
);
given()
.headers(headers)
.when()
.get("/users");Or with a Map (often the easiest for parameterised tests):
Map<String, String> headers = Map.of(
"Accept", "application/json",
"Authorization", "Bearer " + token,
"X-Request-Id", UUID.randomUUID().toString()
);
given()
.headers(headers)
.when()
.get("/users");Both forms compose with per-test header(...) calls — the Map sets the defaults, individual header() calls override or add.
Content-Type — what your body is
Content-Type tells the server how to parse the body of the request you're sending. Rest Assured has a dedicated contentType() method that takes a ContentType enum value or a raw string:
// JSON — by far the most common
given()
.contentType(ContentType.JSON)
.body("{\"name\":\"Alice\"}")
.post("/users");
// URL-encoded form (login forms, OAuth token endpoints)
given()
.contentType(ContentType.URLENC)
.formParam("username", "alice")
.formParam("password", "pass123")
.post("/login");
// XML (legacy or SOAP-adjacent endpoints)
given()
.contentType(ContentType.XML)
.body("<user><name>Alice</name></user>")
.post("/users");
// Multipart for file uploads (covered in Lesson 4)
given()
.contentType("multipart/form-data")
.multiPart("file", new File("photo.jpg"));The ContentType enum covers the common cases. For exotic types (application/vnd.company.v3+json, anyone?) pass a raw string — .contentType("application/vnd.company.v3+json") works identically.
Accept — what you want back
Accept is the mirror image of Content-Type — it tells the server what format you'd like the response in. Many APIs honour it for content negotiation (Accept: application/xml returns XML even if the default is JSON):
given()
.accept(ContentType.JSON) // "I want JSON back, please"
.contentType(ContentType.JSON) // "And here's JSON in my body"
.when()
.post("/users");The two methods are independent — you can accept one format and contentType another. A common pattern: send application/json and accept application/vnd.api+json for JSON:API-style responses.
The headers every API tester reaches for
Headers you'll set or assert on most often
| What it does | Example value | Where it shows up | |
|---|---|---|---|
| Content-Type | Format of the REQUEST body | application/json | Every POST / PUT / PATCH |
| Accept | Format you want for the RESPONSE | application/json | Content negotiation, versioned APIs |
| Authorization | Auth credential | Bearer eyJhbGc... or Basic dXNlcjpwYXNz | Almost every secured endpoint |
| X-Request-Id | Trace ID for log correlation | UUID like 7f3e... | Distributed tracing, debugging in prod |
| User-Agent | Identifies the client | MyTestSuite/1.0 | Server-side allow/deny lists, analytics |
| Cache-Control | Caching behaviour | no-cache, max-age=3600 | CDN / proxy debugging, freshness assertions |
Authorization is what you'll set most often. X-Request-Id is what teams forget to send and then can't trace failed requests through their logs. Cache-Control is what makes load tests fail mysteriously when a CDN is silently caching responses.
Asserting on response headers
Setting headers is half the job; reading them is the other half. The Response object exposes them, and then() lets you assert directly:
given()
.when()
.get("/users/1")
.then()
.statusCode(200)
.header("Content-Type", containsString("application/json"))
.header("X-Request-Id", notNullValue())
.header("Cache-Control", equalTo("no-cache"));To capture a header value for use elsewhere:
String requestId = given()
.when()
.get("/users/1")
.then()
.extract()
.header("X-Request-Id");
System.out.println("Request ID: " + requestId);Useful for tests that follow a request through logs, or that need a Location header from a 201 response to fetch the newly-created resource:
String location = given()
.contentType(ContentType.JSON)
.body(newUser)
.when()
.post("/users")
.then()
.statusCode(201)
.extract()
.header("Location");
// Follow the Location header
given().when().get(location).then().statusCode(200);A custom request header for tracing
Production-grade test suites set a unique X-Request-Id on every request so failed runs can be traced through logs. The pattern:
import java.util.UUID;
@BeforeMethod
public void attachRequestId() {
String requestId = "test-" + UUID.randomUUID();
RestAssured.requestSpecification = new RequestSpecBuilder()
.addHeader("X-Request-Id", requestId)
.build();
System.out.println("Test " + this.getClass().getSimpleName() + " using request ID " + requestId);
}When a CI run fails three months later, copying the request ID from the test output and grepping the production log files leads straight to the corresponding server-side trace. We'll formalise this in Chapter 6 with a proper logging filter.
Cookies — a special header
Cookies travel as the Cookie request header and Set-Cookie response header, but Rest Assured has dedicated methods that are nicer to use:
given()
.cookie("session", "abc123")
.cookie("locale", "en_GB")
.when()
.get("/profile")
.then()
.cookie("session", notNullValue());Most modern APIs use bearer tokens rather than cookies, but cookie-based session auth is still common in monoliths and admin panels.
When the API requires a custom Content-Type
Some APIs use vendor-specific media types like application/vnd.api+json (JSON:API), application/vnd.company.v2+json (versioning), or application/problem+json (RFC 7807 errors). Pass them as raw strings:
given()
.contentType("application/vnd.company.v2+json")
.accept("application/vnd.company.v2+json")
.body(payload)
.when()
.post("/users");Rest Assured doesn't constrain the value — whatever string you pass goes on the wire as-is.
⚠️ Common mistakes
- Forgetting
Content-Typeon POST/PUT/PATCH. Without it, many servers default toapplication/x-www-form-urlencodedand silently fail to parse the JSON body. The test sees a400and you wonder what's wrong with your JSON. Always setcontentType(ContentType.JSON)when you have a JSON body. - Confusing
Content-TypeandAccept. They look similar but say different things:Content-Typedescribes what you're sending;Acceptdescribes what you want back. A request with no body should have noContent-Type; a request expecting JSON should always haveAccept: application/json. - Hardcoding a bearer token. Tests that hardcode
Authorization: Bearer eyJhbGciOi...break the moment that token expires. Build aTokenProvider(Chapter 4 covers this in depth) that fetches and caches tokens, and inject the token via a sharedRequestSpecification.
🎯 Practice task
Wire headers into the test class you've been growing. 25–30 minutes.
- In
UserApiTest(against JSONPlaceholder), add anAccept: application/jsonandUser-Agent: rest-assured-course/1.0header to one of your tests. Run it green. - Add
.header("X-Request-Id", UUID.randomUUID().toString())to every test in the class. (For now, copy-paste; we'll factor it out in Chapter 6.) Confirm the suite is still green. - Read a header from the response. GET
/users/1andextract().header("Content-Type"). Print it. Note the format JSONPlaceholder returns (application/json; charset=utf-8— note the charset). - Write
assertResponseContentTypeIsJson(). Use both.contentType(ContentType.JSON)and.header("Content-Type", containsString("application/json")). Note that the first is stricter (it normalises) and the second is more permissive — both work; pick by your team's style. - Authorisation rehearsal. Write a test that adds
Authorization: Bearer fake-token-for-testing. JSONPlaceholder doesn't enforce auth — but on a real API, this is exactly what you'd do. Run it; confirm it doesn't break (the header is sent but ignored). - Negative content-type test. POST a JSON body but set
contentType(ContentType.XML). Run against a real API (REQRES, for example). Note the response — most will return415 Unsupported Media Typeor400 Bad Request. This is a real test class that catches API regressions where content-type validation is removed. - Stretch: factor your default headers into a
RequestSpecBuilderand callgiven().spec(commonSpec)in every test. The class should shrink visibly. Save the change — we'll build on it in Chapter 6.
Next lesson: JSON request bodies — strings, Maps, POJOs, and which to use when.