In REST Assured, why must given().contentType(...) come before body(diveEntry) in the chain?
answer
- serialization happens inside the call
- the header must already be there
- null content type has its own order
- later contentType only rewrites the header
basics
~20 sREST Assured's body(Object) serializes immediately, reading the Content-Type already on the specification. Call it first and that value is still null, so the no-content-type scan runs. A later contentType(...) then only rewrites the header, because the bytes are already fixed.
solid answer
~40 s`body(Object)` invokes the mapping layer inside the `body(...)` call itself and stores the resulting `String`. The content type it passes in is read from the specification's `Content-Type` header at that instant, so `given().body(entry).contentType(ContentType.XML)` serializes with **no** content type at all. That path has an order of its own — Jackson 3, Jackson 2, Jackson 1, Gson, Jakarta EE, JAXB, Johnzon, Yasson — so with Jackson on the classpath you send JSON bytes under an `application/xml` header and the service rejects a payload that looks correct in the source. Reversing the two calls fixes it: `given().contentType(ContentType.XML).body(entry)` reaches the XML branch and uses Jakarta EE, then JAXB. Nothing in the DSL warns you, because the chain compiles and reads naturally in either order and the failure surfaces as a rejected request rather than a mapping error.
code
java · 12 linesimport static io.restassured.RestAssured.given;
import io.restassured.http.ContentType;
// WRONG: body(...) runs while Content-Type is still unset,
// so the no-content-type scan starts at Jackson 3 and writes JSON
given().body(entry).contentType(ContentType.XML)
.post("/clubs/kraken-divers/dives");
// RIGHT: the header is on the spec before body(...) reads it,
// so the XML branch runs and Jakarta EE (then JAXB) writes the body
given().contentType(ContentType.XML).body(entry)
.post("/clubs/kraken-divers/dives");go deeper
Learn the habit before the theory: put contentType(...) directly after given() and body(...) after it. Being able to say that order matters here is enough at this stage.
Explain the mechanism — body(Object) serializes eagerly and reads the Content-Type header as it stands — and name the separate order REST Assured uses when no content type is set.
Describe how you would catch this across an existing suite: assert on the bytes actually sent, or centralise the content type so no individual test can build a body before it is set.
Frame it as an API-ergonomics risk you design around: a fluent builder whose result depends on call order needs a house convention or a lint rule, not a wiki page that new joiners never read.
## Serialization happens inside the `body(...)` call REST Assured's fluent chain looks declarative, as if the whole specification were assembled first and then executed. `body(Object)` breaks that impression. When you call it, it immediately asks the object-mapping layer for a `String`, stores that string as the request body and returns. By the time the chain reaches `post(...)`, the bytes have existed for several statements. The content type the mapping layer receives is read at that same instant, from the `Content-Type` header already on the specification. So the two chains below are not two spellings of one request: - `given().contentType(ContentType.XML).body(entry)` — the header is present, the XML branch runs. - `given().body(entry).contentType(ContentType.XML)` — the header is not present yet, so the mapping layer is handed a null content type and takes the no-content-type branch. ## What the wrong order actually sends The no-content-type branch is not an error path. It has a defined order of its own and will happily produce a payload: 1. It walks Jackson 3, Jackson 2, Jackson 1, Gson, Jakarta EE, JAXB, Johnzon, Yasson. 2. It stops at the first candidate whose marker class is on the classpath. 3. It throws only if none of the eight is present. On any ordinary dive-log project Jackson is on the classpath, so step 2 stops at Jackson and the body becomes JSON. The later `contentType(ContentType.XML)` call cannot undo that — it only writes a header. The request that goes out is JSON bytes under an `application/xml` header, which the service rejects. Nothing in the test points at the mapper; the symptom is a rejected request that looks correct in the source. ## The three orders, side by side | when `body(Object)` runs, the content type is | branch taken | order tried | |---|---|---| | something containing `json` | JSON | Jackson 3, Jackson 2, Jackson 1, Gson, Johnzon, Yasson | | something containing `xml` | XML | Jakarta EE, JAXB | | absent, or exactly `*/*` | neither | Jackson 3, Jackson 2, Jackson 1, Gson, Jakarta EE, JAXB, Johnzon, Yasson | | anything else | none | throws `IllegalArgumentException` | The third row is the one that catches people. It is not the JSON order followed by the XML order: Jakarta EE and JAXB sit ahead of Johnzon and Yasson in it. A project with only Johnzon and JAXB available and no content type set will serialize to **XML**, which is rarely what the author meant. ## The charset is resolved at the same moment Mapper selection is not the only thing the content type decides in that instant. `body(Object)` also resolves the charset it hands to the mapper, and it does so from the same header: - If the content type carries a `charset=` parameter, that charset is used. - Otherwise `EncoderConfig` is consulted for a default registered against that content type — `application/json` maps to UTF-8 out of the box. - Otherwise the config's `defaultContentCharset()` applies, and that default is ISO-8859-1. So `given().body(entry).contentType("application/json; charset=UTF-16")` misses twice: the writer is chosen by the no-content-type scan *and* the payload is encoded as ISO-8859-1, because neither the declared charset nor the `application/json` default was visible when the mapping ran. Written the other way round, both land. A dive site name with a non-ASCII character is the usual way this second half of the bug is discovered. ## Making it not happen Ordering discipline is the whole fix, and there are three ways to get it for free: - Put `contentType(...)` immediately after `given()` as a house style, so no chain can ever set a body first. - Set the content type once in a shared request specification that every call starts from, so the header is already on the specification before any per-test `body(...)` runs. - Name the mapper explicitly with `body(entry, ObjectMapperType.JAKARTA_EE)` where the format genuinely matters, which removes the content type from the decision entirely. A review habit helps too: in any chain that calls `body(...)` with an object, check that a content type was established earlier in that same chain. ## Where the hazard stops Two of the three object-taking overloads are immune, for different reasons: - `body(Object, ObjectMapperType)` short-circuits the content-type decision. The content type still travels to the mapper as context — it is visible through `ObjectMapperSerializationContext.getContentType()` — but it no longer chooses anything, so the payload is identical whichever order you write. - `body(Object, ObjectMapper)` serializes through the instance you pass and never consults the content type for selection at all. It also does not apply to the byte-carrying overloads: `body(String)`, `body(byte[])`, `body(File)` and `body(InputStream)` have nothing to serialize, so their position in the chain is irrelevant to the payload. The trap is specific to `body(Object)`, which is exactly the overload people reach for most often when posting a dive log.
- What order does REST Assured use when body(Object) runs with no content type set at all?It walks Jackson 3, Jackson 2, Jackson 1, Gson, Jakarta EE, JAXB, Johnzon and Yasson, taking the first present on the classpath. Note that this order interleaves the XML mappers ahead of Johnzon and Yasson, so it is not the JSON order followed by the XML one. If none of the eight is present, `body(Object)` throws instead of sending an empty body.
- Does the same ordering hazard apply to body(entry, ObjectMapperType.JAXB)?No. Naming a type short-circuits the content-type decision, so the same mapper runs whichever order you write the chain in. The content type still reaches the mapper as context, through `ObjectMapperSerializationContext.getContentType()`, but it no longer selects anything and the payload is stable.
saying these in an interview costs you the question
- Thinks the body is serialized when post() is called
- Believes call order never matters in a fluent DSL chain
- Assumes an unset content type makes body(Object) throw
- Expects contentType(XML) after body(...) to re-serialize the payload
- Blames the service when JSON arrives under an XML content type