skip to content

In REST Assured, why does then().contentType(ContentType.JSON) accept a vendor +json media type?

level: seniorimportance: must knowfreq 44%

answer

  1. the enum names a family, not a string
  2. parameters are dropped before resolving
  3. a +json suffix resolves to JSON
  4. the String form is a prefix match
  5. the Matcher form sees the raw value

basics

~20 s

Because the enum overload is a family check. REST Assured strips any parameters, then resolves the media type through ContentType.fromContentType, which maps anything ending in +json, plus application/javascript, text/json and text/javascript, to JSON. The String and Matcher overloads are stricter.

solid answer

~50 s

Because `contentType(ContentType.JSON)` compares media-type **families**, not strings. REST Assured drops everything from the first `;`, lower-cases the rest, and resolves it to a `ContentType` constant: `JSON` matches `application/json`, `application/javascript`, `text/javascript` and `text/json`, and a suffix rule maps anything ending in `+json` to `JSON` as well. So `application/vnd.kayak.v2+json` and `application/json;charset=utf-8` both satisfy it. The other two overloads are stricter. `contentType("application/json")` strips whitespace and then tests a case-insensitive **prefix**, so it tolerates a charset parameter but rejects a vendor type. `contentType(Matcher)` sees the raw header value including parameters, which is why `equalTo("application/json")` fails against `application/json;charset=utf-8`. Pick the strictness the contract actually needs: the enum form when the claim is only "a client can parse this as JSON", and the `String` or `Matcher` form when the exact media type is itself the promise. The practical consequence is that a green `contentType(ContentType.JSON)` is no evidence the media type is unchanged.

code

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

given()
    .baseUri("https://api.paddleport.example/v1")
.when()
    .get("/rentals/9f31c2")
.then()
    .statusCode(200)
    // family check: also true for application/vnd.kayak.v2+json
    .contentType(ContentType.JSON)
    // prefix check: tolerates ;charset=utf-8, rejects a vendor type
    .contentType("application/vnd.kayak.v2+json")
    // raw check: parameters included, nothing normalised away
    .contentType(is("application/vnd.kayak.v2+json;charset=utf-8"));

go deeper

for a junior

Remember that contentType has three overloads and that the ContentType enum form is the loosest of them; reach for it when you only mean 'parseable as JSON'.

for a middle

Explain the resolution: parameters stripped, value lower-cased, matched against the constant's media types, then the +json, +xml and +html suffix rules.

for a senior

Diagnose a suite that stayed green through a media-type migration, and decide which endpoints deserve an exact assertion instead of the family check.

for a principal

Set the house rule for versioned media types: which are contract, who asserts them, and how a migration is proven safe before consumers find out.

## Three overloads, three different comparisons `ValidatableResponseOptions` declares `contentType(ContentType)`, `contentType(String)` and `contentType(Matcher<? super String>)`. They all read the response's `Content-Type`, and they are deliberately not equally strict: | overload | what it compares | passes on `application/vnd.kayak.v2+json` | |---|---|---| | `contentType(ContentType.JSON)` | the media-type **family** the value resolves to | yes | | `contentType("application/json")` | a case-insensitive **prefix** of the value | no | | `contentType(equalTo("application/json"))` | the **raw** value, parameters included | no | ## Why the enum form accepts a vendor type When the argument is a `ContentType` constant, REST Assured does not compare strings. It resolves the actual value into a `ContentType` and compares the two enum constants. That resolution is a family lookup: 1. Any media-type parameters are dropped — everything from the first `;` onward, so `;charset=utf-8` never participates. 2. The remainder is lower-cased and checked against each constant's own media-type list. For `JSON` that list is `application/json`, `application/javascript`, `text/javascript` and `text/json`. 3. Failing that, a **suffix rule** applies: anything ending in `+json` resolves to `JSON`, `+xml` to `XML`, and `+html` to `HTML`. Rule 3 is the answer to the question. `application/vnd.kayak.v2+json` is not in the list, but it ends in `+json`, so it resolves to `ContentType.JSON` and the assertion passes. So does `text/json`, and so does `application/json;charset=utf-8`. That is by design: the enum names a family of media types, and the same resolution decides which parser REST Assured will use for the body. ## Why `equalTo("application/json")` fails The `Matcher` overload hands your matcher the **raw** header value, exactly as received. Most services send a charset parameter, so the actual value is `application/json;charset=utf-8` and an equality matcher against `application/json` fails with: ``` Expected content-type "application/json" doesn't match actual content-type "application/json;charset=utf-8". ``` The `String` overload sits between the two. It strips spaces and tabs from both sides and then tests whether the actual value **starts with** the expected one, case-insensitively. So: - `contentType("application/json")` passes against `application/json;charset=utf-8`. - `contentType("application/json;charset=utf-8")` also passes, because the whole thing is a prefix of itself. - `contentType("json")` fails — a prefix means from the beginning, not anywhere. - `contentType("APPLICATION/JSON")` passes, because the comparison ignores case. ## The same four constants, spelled out It helps to know what the enum actually carries, because the family is wider than the name suggests: - `ContentType.JSON` — `application/json`, `application/javascript`, `text/javascript`, `text/json`, plus anything ending in `+json`. - `ContentType.XML` — `application/xml`, `text/xml`, `application/xhtml+xml`, plus anything ending in `+xml`. - `ContentType.HTML` — `text/html`, plus anything ending in `+html`. - `ContentType.TEXT` — `text/plain` only; a value such as `text/csv` resolves to no constant at all and therefore fails every enum comparison. A value that resolves to nothing is simply unequal to whatever constant you named, so the failure message shows the constant's name against the raw header value and reads a little oddly the first time you see it: ``` Expected content-type "JSON" doesn't match actual content-type "text/csv". ``` ## Choosing strictness on purpose - Use `ContentType.JSON` when the claim is "a client can parse this as JSON". It survives a charset change and a move to a versioned vendor type, which is usually what you want and occasionally the bug. - Use `contentType("application/vnd.kayak.v2+json")` when the exact media type **is** the contract — a versioned type that a consumer negotiates on is a promise, and the prefix comparison still lets the charset ride along. - Use `contentType(Matcher)` when you need something the other two cannot express: `startsWith`, `containsString`, or an exact `is("application/json;charset=utf-8")` including parameters. - Use `header("Content-Type", ...)` only when you deliberately want the plain header path; the dedicated method exists because the media type also drives body parsing. The production lesson is that a green `contentType(ContentType.JSON)` is not evidence that the media type is unchanged. If a kayak-rental endpoint moves from `application/json` to `application/vnd.kayak.v2+json`, every enum-form assertion in the suite stays green while consumers that negotiated the old type break. If that transition is one your tests must catch, name the exact type in a `String` or `Matcher` assertion for the endpoints whose media type is part of the published contract, and keep the tolerant enum form for the rest. A cheap belt-and-braces habit on the handful of endpoints whose media type is negotiated: assert both, the enum form for the family and a `String` form for the exact type, so the first tells you the payload is still parseable and the second tells you the moment the published type moved.

  • Why does contentType(equalTo("application/json")) fail on a response that is plainly JSON?
    The `Matcher` overload is handed the raw header value, and most services send `application/json;charset=utf-8`. Equality against the bare media type therefore fails. Use `contentType("application/json")`, whose prefix comparison ignores the trailing parameter, or write the matcher to expect the parameter, for example `startsWith("application/json")`.
  • Which media types besides application/json satisfy contentType(ContentType.JSON)?
    The constant carries four media types — `application/json`, `application/javascript`, `text/javascript` and `text/json` — and the resolution adds a suffix rule, so anything ending in `+json` resolves to `JSON` too. Parameters are stripped first, so a charset never affects the outcome. `XML` and `HTML` have the equivalent `+xml` and `+html` rules.
  • Is contentType("application/json") a substring match?
    No, it is a prefix match. After spaces and tabs are removed from both values, REST Assured tests whether the actual value starts with the expected one, ignoring case. So `application/json` passes against `application/json;charset=utf-8`, but `json` on its own fails, because a prefix is anchored at the beginning of the value.

saying these in an interview costs you the question

  • Believing contentType(ContentType.JSON) pins the exact media type
  • Expecting equalTo("application/json") to ignore a charset parameter
  • Reading the String overload as a substring or contains comparison
  • Assuming a vendor +json type needs its own registered ContentType constant
  • Treating a green content-type assertion as proof the media type never changed