skip to content

In REST Assured, which library serializes the object you hand to given().body(diveEntry)?

level: juniorimportance: should knowfreq 58%

answer

  1. the request decides, not the object
  2. content type drives the writer
  3. first mapper on the classpath wins
  4. Jackson 3 heads the JSON scan
  5. scalars bypass mapping via toString

basics

~20 s

REST Assured's body(Object) picks its writer from the request Content-Type, not the object's class. A JSON type takes the first of Jackson 3, Jackson 2, Jackson 1, Gson, Johnzon, Yasson on the classpath. An XML type takes Jakarta EE, then JAXB.

solid answer

~50 s

`RequestSpecification.body(Object)` hands the object to REST Assured's object-mapping layer, which chooses a writer from the **request** content type rather than from the object's type. If that content type contains `json`, REST Assured walks the classpath in a fixed order — Jackson 3, Jackson 2, Jackson 1, Gson, Johnzon, Yasson — and uses the first mapper it finds. If it contains `xml`, it tries Jakarta EE and then JAXB. With no content type set at all it uses a third order of its own: Jackson 3, Jackson 2, Jackson 1, Gson, Jakarta EE, JAXB, Johnzon, Yasson. Finding nothing means an exception, not an empty body. Serialization is also eager — it happens inside the `body(...)` call — so the content type has to be on the specification already. Scalars such as `String`, numbers, `Boolean`, enums and `UUID` skip mapping entirely and go through `toString()`.

code

java · 14 lines
java
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.notNullValue;
import io.restassured.http.ContentType;

DiveEntry entry = new DiveEntry("Blue Hole", 38, 22, "EAN32");

given()
    .contentType(ContentType.JSON)   // set first: body(...) reads it immediately
    .body(entry)                     // Jackson 3 > 2 > 1 > Gson > Johnzon > Yasson
.when()
    .post("/clubs/{clubId}/dives", "kraken-divers")
.then()
    .statusCode(201)
    .body("diveId", notNullValue());

go deeper

for a junior

Be ready to say that body(Object) needs a request content type and that the writer comes from the classpath. Naming Jackson or Gson for JSON and Jakarta EE or JAXB for XML is enough at this level.

for a middle

Explain the scan itself: Jackson 3, Jackson 2, Jackson 1, Gson, Johnzon, Yasson for a JSON content type, Jakarta EE then JAXB for an XML one, and a third, different order when no content type is set.

for a senior

Show how you pin the mapper in a shared suite so a transitively added Gson cannot quietly change every payload, and describe how such a swap would surface in CI before it reaches a release.

for a principal

Own the policy question: whether the organisation declares one serializer for all API suites or lets each project's classpath decide, and what the second option costs when teams add mapping libraries independently.

## What `body(...)` does with a Java object `RequestSpecification` declares seven `body(...)` overloads. Four of them take bytes you already have — `body(String)`, `body(byte[])`, `body(File)`, `body(InputStream)` — and are handed to the transport untouched. Three take a Java object and reach REST Assured's object-mapping layer: - `body(Object)` — let the request content type choose the writer. - `body(Object, ObjectMapperType)` — name one of the eight built-in mappers. - `body(Object, ObjectMapper)` — supply your own writer instance. This question is about the first. Handed a `DiveEntry`, `body(Object)` turns it into a `String` there and then, stores that string as the request body, and returns the specification so the chain can continue. Serialization is not deferred until `post(...)` runs. ## The content type picks the writer The selector is the **request** `Content-Type` — the header already sitting on the specification when `body(...)` executes — and nothing about the object itself. REST Assured lower-cases that value and tests it for a substring: - If it contains `json`, the JSON branch runs. - If it contains `xml`, the XML branch runs. - If it is absent, or is exactly `*/*`, a third branch runs that tries everything. - If it is none of those, `body(Object)` throws rather than guessing a format. Inside a branch the rule is simply "the first candidate present on the classpath wins". REST Assured probes each one by looking for a marker class — `com.fasterxml.jackson.databind.ObjectMapper` for Jackson 2, `com.google.gson.Gson` for Gson, `jakarta.xml.bind.Binder` for Jakarta EE — so what really decides the shape of your payload is your dependency tree. | request content type | order REST Assured tries | |---|---| | contains `json` | Jackson 3, Jackson 2, Jackson 1, Gson, Johnzon, Yasson | | contains `xml` | Jakarta EE, JAXB | | absent or `*/*` | Jackson 3, Jackson 2, Jackson 1, Gson, Jakarta EE, JAXB, Johnzon, Yasson | The third order is not the first two concatenated: it puts the XML mappers ahead of Johnzon and Yasson. It is a separate list in the source, and it is worth knowing precisely, because it is the one that runs whenever a `DiveEntry` is serialized before a content type was set. ## The eight named mappers behind the scan `ObjectMapperType` has exactly eight constants — `JACKSON_3`, `JACKSON_2`, `JACKSON_1`, `GSON`, `JAXB`, `JOHNZON`, `JSONB`, `JAKARTA_EE` — and the scan above is just those constants in a fixed sequence. Two pairs are easy to confuse: - `JAXB` probes `javax.xml.bind.Binder` while `JAKARTA_EE` probes `jakarta.xml.bind.Binder`; they are the pre- and post-namespace artifacts, not two implementations of the same thing. - `JSONB` is satisfied only by Eclipse Yasson, so a different JSON-B implementation on the classpath does not enable it. `JACKSON_3` arrived with the 6.0.0 baseline, which also moved the library to Java 17 and Groovy 5. It heads both JSON scans, so adding Jackson 3 to a dive-log suite that already had Jackson 2 changes which library writes every payload without a single test being edited. ## What never reaches a mapper `body(Object)` does not push everything through the mapping layer. It first asks whether the argument is a serialization candidate and answers no for `String`, any `Number`, `Boolean`, `Character`, any `enum`, `Locale`, `Class` and `UUID`. Those go through `toString()` and become a plain string body, exactly as if you had called `body(String)`: - `body("{\"siteName\":\"Blue Hole\"}")` sends that text unchanged, with no re-quoting. - `body(GasMix.EAN32)` sends `EAN32`, not `"EAN32"`. - `body(diveId)` where `diveId` is a `UUID` sends the bare identifier with no JSON quoting. A `Map` *is* a candidate, which is why assembling a payload as a `Map<String, Object>` and passing it to `body(...)` produces a JSON document rather than a `toString()` dump. ## When no mapper is found Every branch ends in a throw, never a silent fallback: 1. A JSON content type with no JSON mapper on the classpath raises `IllegalStateException`, naming Jackson (Databind), Gson, Johnzon and Yasson as the acceptable dependencies. 2. An XML content type with neither Jakarta EE nor JAXB raises `IllegalStateException` as well. 3. A content type matching neither word raises `IllegalArgumentException`, saying it cannot determine how to serialize that content type. All three fire while you are still assembling the specification, so no request leaves the process. The failure shows up as a local exception in the test, not as a server response — worth remembering when you are reading a stack trace and hunting for an HTTP status code that was never issued.

  • What does REST Assured do when the request content type is JSON but no JSON mapper is on the classpath?
    The JSON branch runs out of candidates and throws `IllegalStateException`, with a message telling you to put Jackson (Databind), Gson, Johnzon or Yasson on the classpath. Nothing is sent: the failure happens while the specification is still being built, so there is no HTTP call and no server status code involved.
  • Does body(Object) serialize a String the same way it serializes a POJO?
    No. `body(Object)` treats `String`, `Number`, `Boolean`, `Character`, enums, `Locale`, `Class` and `UUID` as non-candidates and routes them through `toString()`, which is identical to calling `body(String)`. So a `String` that already holds JSON is sent verbatim, and an enum is sent as its bare name with no quotes around it.

It is like addressing the envelope before writing the letter: REST Assured reads the label already stuck on the request and picks a writer to match, never opening the object to see what is inside.

saying these in an interview costs you the question

  • Claims REST Assured always uses Jackson whatever the content type says
  • Thinks the POJO's mapping annotations select which library runs
  • Expects a silent fallback to XML when no JSON mapper is present
  • Believes the object is serialized when post() is called
  • Assumes body(someUuid) is wrapped as a quoted JSON string