In REST Assured, how do you assert that an XML response body is valid against an XSD?
answer
- REST Assured ships matchers of its own
- validate side, and no path argument
- RestAssuredMatchers.matchesXsdInClasspath
- matcher receives response.asString()
basics
~20 sREST Assured ships matchesXsd and matchesXsdInClasspath in io.restassured.matcher.RestAssuredMatchers. Passed into then().body(Matcher) with no path argument, either one validates the entire response body against the schema. matchesXsd accepts a String, InputStream, Reader or File; matchesXsdInClasspath takes a classpath resource path.
solid answer
~40 s`io.restassured.matcher.RestAssuredMatchers` is where REST Assured's own Hamcrest matchers live, and two of them cover XSD validation: `matchesXsd(...)`, with overloads taking a `String`, `InputStream`, `Reader` or `File`, and `matchesXsdInClasspath(String path)`, which loads the schema through the thread context classloader. You hand the result to the validate-side `then().body(Matcher)` overload — the one with **no** GPath argument — so REST Assured passes the whole response body, as a string, straight to the matcher. Internally the matcher builds its own `SchemaFactory` for the W3C XML Schema namespace, compiles your `.xsd` and runs a `Validator` over the body; it never consults `XmlConfig`, which governs `XmlPath` parsing instead. A classpath path may start with or without a leading slash, and the check is all-or-nothing: it says the document matched the schema, nothing about individual fields.
code
java · 9 linesimport static io.restassured.RestAssured.get;
import static io.restassured.matcher.RestAssuredMatchers.matchesXsdInClasspath;
import static org.hamcrest.Matchers.equalTo;
get("/sweep-schedule")
.then()
.statusCode(200)
.body(matchesXsdInClasspath("sweep-schedule.xsd"))
.body("sweepSchedule.booking[0].chimneyId", equalTo("CH-2081"));go deeper
Be ready to name the import — io.restassured.matcher.RestAssuredMatchers — and to write the assertion as then().body(matchesXsdInClasspath("...")) with no path argument.
Explain why the no-path body(Matcher) overload hands over the raw body string, and why that means content type and parser registration are irrelevant to a schema check.
Show judgment about what a schema assertion is worth in a suite: it catches shape drift cheaply but proves no values, so pair it with targeted field assertions rather than treating it as coverage.
Own the question of where schema checking belongs at all — one assertion inside a functional test, a shared response specification, or a separate gate — and be able to defend the split you chose.
## The two matchers, and where they live REST Assured ships a handful of its own Hamcrest matchers in `io.restassured.matcher.RestAssuredMatchers`, and XSD validation is two of them. They are part of the core `rest-assured` artifact, so nothing extra has to be added to the build to use them. The XSD half of that class is: - `matchesXsd(String xsd)` — the schema already in memory as text - `matchesXsd(InputStream xsd)` — a stream you opened yourself - `matchesXsd(Reader xsd)` — a character stream - `matchesXsd(File xsd)` — a schema sitting on disk - `matchesXsdInClasspath(String path)` — a schema packaged alongside the tests All of them return `XmlXsdMatcher`, which is a `BaseMatcher<String>`. `matchesXsdInClasspath` is a thin wrapper: it opens a classpath stream and calls the `InputStream` overload. ## Attaching one to a response Schema validation goes through the **validate side** of the DSL, on the no-path `ValidatableResponse.body(Matcher)` overload: ```java get("/sweep-schedule").then().body(matchesXsdInClasspath("sweep-schedule.xsd")); ``` The distinction between the two `body(...)` overloads matters here. `body(String path, Matcher)` evaluates a GPath expression first and matches against the extracted value. `body(Matcher)` has no path at all, so REST Assured hands the matcher `response.asString()` — the entire body, unparsed. Two useful consequences fall out of that: - The schema check does **not** depend on the response's `Content-Type`. With no path to evaluate, REST Assured never builds a content parser, so a chimney-sweep endpoint that serves XML as `text/plain` still validates without a `RestAssured.registerParser(...)` call. - The matcher sees the body exactly as it arrived, prolog and whitespace included. ## What runs inside the matcher `XmlXsdMatcher.matches` is short, and it is the standard JAXP sequence: 1. Build a schema factory with `SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)`. 2. Install an `LSResourceResolver` on it, if one was supplied through the matcher's `using(...)` hook. 3. Compile the schema with `factory.newSchema(...)` over whichever source you handed in. 4. Create a `Validator` from the compiled schema and run it over a `StreamSource` wrapping the response body. Note what is *absent* from that list: the matcher builds its own `SchemaFactory` and never reads REST Assured's `XmlConfig`. `XmlConfig.namespaceAware(...)`, `validating(...)`, `allowDocTypeDeclaration(...)` and `disableLoadingOfExternalDtd()` shape how `XmlPath` parses a document for GPath extraction. They have no effect whatsoever on `matchesXsd`, which is a frequent source of "I turned that on and nothing changed". ## Loading the schema from the classpath `matchesXsdInClasspath` resolves through the thread's context classloader and then falls back, so all of these find `src/test/resources/sweep-schedule.xsd`: - `matchesXsdInClasspath("sweep-schedule.xsd")` - `matchesXsdInClasspath("/sweep-schedule.xsd")` — the leading slash is stripped and retried - `matchesXsdInClasspath("schemas/sweep-schedule.xsd")` for a nested folder If the resource genuinely is not there the loader yields nothing and the failure surfaces from the schema factory rather than as a readable assertion message. Before you suspect the schema itself, check that the file is under a resources root that actually reaches the test classpath. ## Reusing a matcher instance The matcher holds on to whatever source you gave it, and three of the overloads wrap a one-shot stream: | Overload | What the matcher holds | Safe to reuse | |---|---|---| | `matchesXsd(String)` | `StreamSource` over a `StringReader` | no | | `matchesXsd(InputStream)` | `StreamSource` over that stream | no | | `matchesXsd(Reader)` | `StreamSource` over that reader | no | | `matchesXsd(File)` | the `File` object itself | yes | | `matchesXsdInClasspath(path)` | a stream opened per call | yes, per call | So call the factory method inside each test rather than hoisting one `XmlXsdMatcher` into a `static final` field, or hand it a `File` if you really want one shared instance. ## What the assertion actually proves For a chimney-sweep scheduling API, `get("/sweep-schedule").then().body(matchesXsdInClasspath("sweep-schedule.xsd"))` proves the whole document is structurally valid: declared elements only, correct nesting and ordering, cardinality respected, and simple types honoured — `sweepDate` really is an `xs:date`, `bookingRef` really matches its pattern. It proves nothing about any particular value: not that a booking exists for chimney `CH-2081`, not that the schedule is non-empty, not that the sweep was assigned to anyone. Those stay ordinary `body("path", matcher)` assertions, and you chain the schema check and the value checks in the same `then()` block. A useful way to split the labour on this endpoint: - The schema catches the service dropping `<flueType>` from every booking, or emitting `2026-13-40` where an `xs:date` is declared. - The schema does not catch an empty `<sweepSchedule/>`, or every sweep being scheduled against the wrong chimney. Because the check costs one line and says nothing about values, it is a good candidate for a shared response specification covering every XML endpoint in the suite, with the value assertions left where they belong, in the individual tests that care about them.
- Does matchesXsd need the response to be served with an XML content type?No. `body(Matcher)` carries no GPath expression, so REST Assured never builds a content parser and simply hands the matcher `response.asString()`. A chimney-sweep endpoint returning XML under `text/plain` validates fine, and no `RestAssured.registerParser(...)` or `defaultParser` setting is required for the schema check itself.
- Which XmlConfig settings change how matchesXsd parses the response?None of them. `XmlXsdMatcher` constructs its own `SchemaFactory` and `Validator` and never reads `RestAssuredConfig`. `XmlConfig.namespaceAware(...)`, `validating(...)`, `allowDocTypeDeclaration(...)` and `disableLoadingOfExternalDtd()` govern `XmlPath` parsing for GPath extraction, which is a separate code path from the schema matcher.
saying these in an interview costs you the question
- Thinks a separate artifact is needed before matchesXsd resolves
- Passes a GPath expression alongside the schema matcher
- Believes XmlConfig settings configure how matchesXsd parses
- Assumes the response must be registered as XML first
- Reuses one matchesXsd(InputStream) matcher across many tests
- Expects the schema check to also assert individual field values