Your REST Assured schema assertion throws JsonSchemaValidationException instead of failing — how do you diagnose it?
answer
- exception, not assertion failure
- read the cause chain
- matcher sees the raw body string
- an HTML error page is not JSON
- assert status before the schema
basics
~20 sA thrown JsonSchemaValidationException means the matcher could not complete, not that the payload mismatched. Its cause is the real fault, usually a body that is not JSON, an empty body, or an unloadable schema. A genuine mismatch raises AssertionError instead.
solid answer
~40 sSeparate the two outcomes first. When `JsonSchemaValidator` finishes and the payload does not conform, the matcher returns false and REST Assured raises an `AssertionError` reading *Response body doesn't match expectation*, with the wrapped library's processing messages in the `Expected:` block. When it cannot finish, `matchesSafely` catches the exception and rethrows it as `io.restassured.module.jsv.JsonSchemaValidationException` — a `RuntimeException` whose **cause** carries the real story. The usual causes on a peatland carbon-survey suite are a body that is not JSON at all (an HTML gateway page, an empty `204`), or a schema that cannot be read. It happens because the path-less `body(Matcher)` overload hands the matcher `response.asString()` — the raw text, with no content-type check. Fix it by reading the cause, then by ordering `statusCode(200)` and the content type ahead of the schema check.
code
java · 13 linesimport static io.restassured.RestAssured.given;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
// a Java then() chain stops at the first failing expectation,
// so cheap assertions first keeps a 502 HTML page out of the validator
given()
.accept(ContentType.JSON)
.when()
.get("/surveys/{surveyId}", "PEAT-2291")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body(matchesJsonSchemaInClasspath("schemas/carbon-survey-schema.json"));go deeper
Learn to read the exception type first: an AssertionError means the payload mismatched, while JsonSchemaValidationException means the check never ran. Then look at the cause.
Explain the mechanism: the path-less body(Matcher) overload passes response.asString(), the matcher parses that text itself, and matchesSafely wraps anything it catches. That is why a non-JSON body throws.
Demonstrate the production fix, not just the diagnosis — ordering cheap expectations ahead of the schema check, capturing the raw body, and keeping matcher instances out of shared state in parallel runs.
Own how the suite reports these failures at all: what a CI failure must show for someone who was not there to act on it, and where the boundary sits between a broken environment and a broken payload.
## Two very different outcomes The first move is to stop reading the exception as a validation result. REST Assured's schema matcher has two exits and they mean opposite things. - **The matcher completed and said no.** `matchesSafely` returned `false`, REST Assured raised an `AssertionError`, and the message begins *"Response body doesn't match expectation."* This is a real schema mismatch. - **The matcher could not complete.** `matchesSafely` wraps its whole body in a `try`/`catch (Exception e)` and rethrows anything it catches as `io.restassured.module.jsv.JsonSchemaValidationException`, a plain `RuntimeException`. Nothing was compared. An `AssertionError` means *the payload is wrong*. A `JsonSchemaValidationException` means *the test could not ask the question*. Editing the schema in response to the second is the classic wasted afternoon. ## What the matcher is actually handed `JsonSchemaValidator extends TypeSafeMatcher<String>`. Passed to the path-less `body(Matcher<?>, Matcher<?>...)` overload, REST Assured calls `matcher.matches(response.asString())` — the response body **as raw text**, exactly as it arrived. Three things follow: - The response content type is never consulted. A `text/html` body reaches the matcher just as readily as `application/json`. - The GPath engine is not involved, so nothing pre-parses or pre-validates the payload. - The matcher parses the text itself, and that parse is inside the `try` block — so a non-JSON body becomes an exception, not a failure. That is why a peatland carbon-survey call that hits a proxy and returns a `502` HTML page produces `JsonSchemaValidationException` rather than a readable assertion. ## Reading the exception Work down the cause chain; the wrapper adds no message of its own. 1. **A JSON parse error naming the body** — the response was not JSON. Check the status code and content type that actually came back. 2. **A JSON parse error naming the schema** — the schema document is malformed. Note the timing: `String`, `Reader` and `File` schemas load eagerly while the matcher is built, whereas `URL` and `URI` schemas — which includes every `matchesJsonSchemaInClasspath` call — are read at match time, so a broken classpath schema surfaces here rather than at construction. 3. **A processing error from the wrapped library** — the schema is readable but the library cannot use it, for instance a reference it cannot resolve or a keyword the configured draft does not support. 4. **An empty-body parse failure** — a `204` or a `304` reached the matcher because the assertion was written unconditionally. ## Reading a real mismatch When it *is* a mismatch, the detail comes from the matcher's `describeTo`, which appends the wrapped library's processing messages. REST Assured's default `MatcherConfig` error description type is `REST_ASSURED`, which builds the failure text as *"Response body doesn't match expectation."* followed by an `Expected:` block containing the matcher's description and an `Actual:` block containing the body. So the per-violation detail lives under `Expected:`, which reads oddly the first time — it is the matcher describing why it objected, not a literal expectation. The matcher stores that report in an instance field during matching. Practical consequence: build the matcher inline in the assertion. A `static final Matcher SCHEMA = matchesJsonSchemaInClasspath(...)` shared across parallel tests can print another thread's messages, because the field is overwritten by whichever validation ran last. ## Ordering the chain so failures stay readable A Java `then()` chain evaluates expectations in order and stops at the first failure, so put the cheap ones first: ```java given().accept(ContentType.JSON) .when().get("/surveys/{surveyId}", "PEAT-2291") .then().statusCode(200) .contentType(ContentType.JSON) .body(matchesJsonSchemaInClasspath("schemas/carbon-survey-schema.json")); ``` Now a `502` HTML page fails on `statusCode(200)` with a message that names the status, and never reaches the validator. This one ordering change removes most of the exceptions a schema suite throws in CI. ## What `checkedValidation` does and does not change `checkedValidation(false)`, whether set per assertion through `using(settings().with().checkedValidation(false))` or globally on `JsonSchemaValidator.settings`, switches the wrapped library from its checked validation call to its unchecked one. It is a choice about how a **processing** problem is raised inside that library. It does not make a mismatching payload pass, it does not suppress the assertion, and it is not a way to make a flaky schema test green. ## A short checklist - Read the exception **type** before the message: `AssertionError` versus `JsonSchemaValidationException`. - Walk the cause chain; the wrapper contributes nothing but the type. - Log or capture the raw body once — the matcher saw text, so you should look at text. - Confirm the schema resolves and parses, independently of the request. - Assert status and content type before the schema, in that order. - Build the matcher inline rather than sharing one instance across threads.
- The body is JSON and the schema loads, yet the failure message carries no per-violation detail. Why?The detail comes from the matcher's `describeTo`, which prints the wrapped library's processing messages only when a report exists. If the report is empty the message collapses to the header line. Widen the picture by reading the `Actual:` block REST Assured prints, which carries the exact body that was validated, and by checking that status and content type are what you assumed.
- Why can a shared static schema matcher print the wrong messages in a parallel run?The validation report is stored in an instance field on the matcher and overwritten on every `matchesSafely` call, then read later by `describeTo` when the failure text is built. One matcher instance shared across threads therefore has a single report slot, and the message you read can belong to whichever validation finished last. Build the matcher inside the assertion instead.
saying these in an interview costs you the question
- Treats the exception as proof the payload broke the schema
- Ignores the cause chain and starts rewriting the schema
- Assumes the matcher inspects the response content type first
- Puts the schema assertion ahead of statusCode in the then() chain
- Expects checkedValidation(false) to turn a mismatch into a warning
- Shares one matcher instance across parallel tests