With REST Assured, how do you pull values out of a response — for example to reuse an id created by one API call in the next request — and how do you turn a response body into a typed object?
answer
- extract() = exit the DSL back to Java
- assert statusCode BEFORE extract
- path() / response() / as(Class) / as(TypeRef)
- jsonPath().getInt/getList for explicit types
- follow the Location header instead of rebuilding URLs
basics
~20 sEnd the chain with .extract(). Use extract().path("id") for a single GPath value, extract().response() for the whole Response (status, headers, body, time), or extract().as(User.class) to deserialize into a POJO. Then pass the value into the next given().
solid answer
~40 s`then()` validation ends with `extract()`, which hands control back to Java: - `int id = given()...post("/users").then().statusCode(201).extract().path("id");` — a single GPath value, inferred to the target type. - `Response r = ...extract().response();` — then `r.getStatusCode()`, `r.getHeader("Location")`, `r.getTime()`, `r.jsonPath().getList("items.id")`, `r.asString()`. - `User u = ...extract().as(User.class);` — deserializes the body with Jackson/Gson (or JAXB for XML). For generics use `extract().as(new TypeRef<List<User>>() {})` or `jsonPath().getList("", User.class)`. The key discipline is to keep `statusCode(...)` before `extract()`, so a failed call fails on the status rather than producing a confusing null further down. For chained flows, extract the id once, then feed it as a path param. Beware of turning tests into long chained scripts — each extra hop is another way for the test to fail for reasons unrelated to what it is checking.
code
java · 20 linesimport static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static org.hamcrest.Matchers.equalTo;
int id = given()
.contentType(JSON)
.body(new CreateUser("ann"))
.when()
.post("/users")
.then()
.statusCode(201)
.extract().path("id");
given()
.pathParam("id", id)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("name", equalTo("ann"));go deeper
Know that extract() ends the chain and that path("id") gives you a value you can pass to the next request.
Distinguish path/response/as(TypeRef), explain object-mapper selection, and know why the status assertion belongs before the extraction.
Talk about keeping flows short, setting up prerequisite state outside the test under check, and avoiding shared mutable state so the suite can run in parallel.
Decide whether typed client models or raw path extraction is the right coupling for the suite, and where response-shape ownership lives across teams.
## Why extract() exists The `then()` stage is purely declarative: matchers pass or fail. Real API tests often need the value itself — the id of the resource just created, a token, a `Location` header to follow. `extract()` is the bridge from the DSL back into ordinary Java. It is the terminal call of the validation stage, which means every assertion you put before it still runs. That ordering matters: put `statusCode(201)` before `extract()`, and a 500 response fails the test at the status assertion with a clear message. Extract first and assert later, and you get a `null` id and a confusing NullPointerException three lines away from the real cause. ## The three extraction forms **1. A single value by path** ```java int id = given().body(payload).contentType(JSON) .when().post("/users") .then().statusCode(201) .extract().path("id"); ``` `path(...)` takes the same GPath expression used in `body(...)` and returns the value with an inferred generic type. Because the type is inferred from the assignment target, a mismatch (assigning a JSON string to an `int`, or a `Float` to a `Double`) surfaces as a ClassCastException at runtime, not a compile error. `extract().jsonPath().getInt("id")` and `getList`, `getMap`, `getString` are the explicitly-typed alternatives and are safer for numbers. **2. The whole Response object** ```java Response response = given().when().get("/users").then().statusCode(200).extract().response(); String location = response.getHeader("Location"); long millis = response.getTime(); List<Integer> ids = response.jsonPath().getList("items.id", Integer.class); String raw = response.asString(); ``` Use this when you need several things from one call, or need headers, cookies, timing, or the raw body. `response.then()` lets you re-enter the validation DSL on an already-captured response, which is handy in helper methods that return a `Response` and let each caller assert what it cares about. **3. Deserialization into your own types** ```java User user = ...extract().as(User.class); List<User> users = ...extract().as(new TypeRef<List<User>>() {}); ``` `as(...)` uses an object mapper chosen from the classpath: Jackson Databind, Jackson 1, Gson or Johnzon for JSON; JAXB for XML. You can override it per call (`as(User.class, ObjectMapperType.GSON)`) or globally via `RestAssured.config().objectMapperConfig(...)`, which is also where you register a customized `ObjectMapper` (date formats, `FAIL_ON_UNKNOWN_PROPERTIES`, naming strategies). Deserialization is itself a form of assertion: if the payload no longer binds to the model, the test fails loudly. That is an argument for using typed extraction on the responses whose shape you care about, and raw path extraction for one-off values. The same mapper machinery runs in reverse on the request side — `body(new CreateUser("ann"))` serializes the POJO, provided the content type is set, otherwise REST Assured cannot pick a mapper. ## Chaining calls in a flow A create-then-read flow is the canonical use: ```java int id = given().contentType(JSON).body(new CreateUser("ann")) .when().post("/users") .then().statusCode(201).extract().path("id"); given().pathParam("id", id) .when().get("/users/{id}") .then().statusCode(200).body("name", equalTo("ann")); ``` An alternative for hypermedia-ish APIs is to follow the `Location` header instead of reconstructing the URL: ```java String location = ...extract().header("Location"); given().when().get(location).then().statusCode(200); ``` ## Practical cautions - **Keep flows short.** Every extracted value adds a dependency between steps: if step 2 fails, step 5 never runs and the failure message points at the wrong place. Prefer setting up prerequisite state via a helper (or directly, if you have a safe way) and keep the assertions focused on the one endpoint under test. - **Do not share extracted state across test methods** via mutable static fields to "save a call". That makes tests order-dependent and impossible to run in parallel or in isolation. - **Beware of consuming the body twice.** Reading `asString()` and then re-parsing is fine because REST Assured buffers by default, but if you disable buffering for large payloads (`config().httpClient(...)` streaming setups) the stream can be consumed once only. - **Numeric types again.** `extract().path("price")` on `9.99` gives a `Float` by default; assigning it to a `Double` throws ClassCastException. Use `jsonPath().getDouble("price")` or configure `numberReturnType`.
- Why does it matter whether statusCode(201) comes before or after extract()?Assertions placed before `extract()` run first, so a failed request fails the test at the status assertion with an accurate message. If you extract first, a 500 error body has no `id` field, so the extraction yields null and the test blows up later with a NullPointerException or a ClassCastException that hides the real cause. Asserting the status before extracting is the difference between a two-second diagnosis and a ten-minute one.
- How do you deserialize a JSON array response into List<User> given Java's type erasure?`extract().as(List.class)` loses the element type and gives you a list of maps. Use `extract().as(new TypeRef<List<User>>() {})`, which captures the generic type via an anonymous subclass, or `extract().jsonPath().getList("", User.class)`, which passes the element type explicitly. Both route through the same object mapper (Jackson or Gson) chosen from the classpath.
saying these in an interview costs you the question
- Extracting the id before asserting the status code, then debugging a NullPointerException instead of seeing the 500.
- Storing extracted ids in static fields shared between test methods, making the suite order-dependent.
- Using extract().as(List.class) and expecting typed elements rather than a list of maps.
- Assigning extract().path("price") straight to a Double when REST Assured returns a Float, then calling it a library bug.
- Building ten-step chained scripts where a failure anywhere reports at the wrong endpoint.