skip to content

In REST Assured, what does matchesDtd do to the response body before it validates it?

level: middleimportance: should knowfreq 33%

answer

  1. it does not validate the body as received
  2. a temporary file is written per assertion
  3. DOCTYPE_SYSTEM is set on the transformer
  4. second parse, this time validating
  5. the error handler throws on warnings too

basics

~20 s

matchesDtd parses the response into a DOM, then writes your DTD to a temporary file. It re-serialises the document with a DOCTYPE pointing at that file and re-parses it through a validating DocumentBuilder. Any warning or error throws.

solid answer

~40 s

`RestAssuredMatchers.matchesDtd(...)` does not simply feed the body to a validator. It parses the response into a DOM with a plain `DocumentBuilder`, writes the DTD you supplied to a temporary file, then runs an identity `Transformer` over the DOM with `OutputKeys.DOCTYPE_SYSTEM` set to that file's path — producing XML text that now begins with a `DOCTYPE` referring to your DTD. It flips the same `DocumentBuilderFactory` to `setValidating(true)`, installs an error handler that throws on **warning, error and fatalError** alike, and re-parses the transformed text; the temp file is deleted in a `finally` block. Two things follow: whatever `DOCTYPE` the server sent is superseded, so you are always validating against your copy, and the DTD's declared root element must match the response's document element or the parse fails immediately.

code

java · 7 lines
java
import static io.restassured.RestAssured.get;
import static io.restassured.matcher.RestAssuredMatchers.matchesDtdInClasspath;

get("/sweep-schedule")
    .then()
        .statusCode(200)
        .body(matchesDtdInClasspath("sweep-schedule.dtd"));

go deeper

for a junior

Know that REST Assured can check an XML response against a DTD with matchesDtd or matchesDtdInClasspath, and that the call goes on then().body(...) with no path argument.

for a middle

Explain the round trip: parse to a DOM, write the DTD to a temp file, re-serialise with a DOCTYPE pointing at it, then re-parse through a validating builder that throws on the first problem.

for a senior

Judge when a DTD check earns its place at all. It is cheap and flat, gives one violation per run, and cannot express types or cardinality the way a schema can — say what you would use instead and why.

for a principal

Decide whether document-shape assertions inside functional tests are the right place for contract enforcement in your suite, and where that responsibility sits relative to the checks the pipeline already runs.

## What matchesDtd is handed `io.restassured.matcher.RestAssuredMatchers` exposes five DTD entry points, and unlike the XSD half they are all declared to return a plain `Matcher<String>`: - `matchesDtd(String dtd)` — the declarations as text - `matchesDtd(InputStream dtd)` - `matchesDtd(File dtd)` - `matchesDtd(URL url)` - `matchesDtdInClasspath(String path)` — with or without a leading slash Like the XSD matchers, they go on the no-path `then().body(Matcher)` overload, so the matcher receives the whole response body as a string. What happens next is what makes this leaf worth knowing. ## The five steps it performs 1. Parse the response body into a DOM using a default, non-validating `DocumentBuilderFactory`. This step only checks well-formedness. 2. Write the DTD you supplied to a temporary file created with `File.createTempFile("restassured", "temp")` and marked `deleteOnExit()`. 3. Run an identity `Transformer` over the DOM with `OutputKeys.DOCTYPE_SYSTEM` set to that temp file's path, serialising the document to a `StringWriter`. The output text now opens with a `DOCTYPE` naming the document element and pointing at your DTD. 4. Flip the same factory to `setValidating(true)`, build a second `DocumentBuilder` from it, and install an error handler whose `warning`, `error` and `fatalError` methods all rethrow the `SAXParseException` they were given. 5. Re-parse the transformed text through that validating builder, then delete the temp file in a `finally` block. If nothing threw, the matcher returns `true`. ## Why the response's own DOCTYPE stops mattering DTD validation in XML is normally driven by the document's own `DOCTYPE` declaration. Step 3 replaces that: the output property wins over whatever the source document declared, so the re-serialised text points at your temp file regardless of what the chimney-sweep service sent. That is usually what you want in a test — you are asserting against the DTD **you** control, not the one the server happens to advertise — but it does mean the assertion cannot tell you whether the service pointed at the right DTD in the first place. ## The error handler is stricter than you expect The handler installed in step 4 throws on all three severities, including `warning`. A `DocumentBuilder` reports warnings for things a validator would otherwise let slide, so a DTD-based check can fail on conditions an XSD check would not surface at all. Combined with the fact that a validating parser stops at the first problem it throws on, the practical picture is: - You learn about one violation per run, not a list of them. - A warning is as fatal as an error. - The message is the parser's own — `Element type "booking" must be declared.` is the classic one — and it is locale-sensitive, which is why REST Assured's own tests pin the default locale before asserting on the text. ## Where it differs from matchesXsd | | `matchesDtd` | `matchesXsd` | |---|---|---| | Declared return type | `Matcher<String>` | `XmlXsdMatcher` | | Resolver hook | none | `using(...)` / `with(...)` | | Engine | validating `DocumentBuilderFactory` | `SchemaFactory` + `Validator` | | Body handling | re-serialised with a new `DOCTYPE` | validated as received | | Source overloads | String, InputStream, File, URL | String, InputStream, Reader, File | Neither reads `XmlConfig`. Its `namespaceAware`, `validating`, `allowDocTypeDeclaration` and `disableLoadingOfExternalDtd` settings shape `XmlPath` parsing for GPath extraction and have no effect on either matcher. ## Practical consequences For a chimney-sweep scheduling API whose `GET /sweep-schedule` returns a `<sweepSchedule>` root: - Your `sweep-schedule.dtd` must declare `sweepSchedule` as its root element. The generated `DOCTYPE` names the response's document element, so a DTD written for `<bookings>` fails immediately with a "must be declared" error. - The matcher creates and deletes a temporary file on every assertion, so a very large DTD is paid for per test rather than compiled once. - Hand it the wrong artefact and the error names the mismatch clearly: an XSD passed to `matchesDtd` fails because the markup declarations are not well-formed DTD, and a DTD passed to `matchesXsd` fails because the markup preceding the root element is not a schema. - Because a source given as a `String`, `InputStream` or `Reader` is read once, build the matcher inside each test rather than caching one instance in a static field. If your contract is a single flat DTD and you only need shape checking, `matchesDtdInClasspath("sweep-schedule.dtd")` is genuinely the shortest thing that works. If the contract spans files or you want types and cardinality expressed properly, the XSD side is the one with the resolver hook and the richer vocabulary.

  • If the response already carries a DOCTYPE, which DTD is actually applied?
    Yours. The identity transformer sets `OutputKeys.DOCTYPE_SYSTEM` to the temporary file holding the DTD you passed, and that output property wins over whatever the source document declared. The re-parsed text therefore validates against your copy, so the assertion says nothing about whether the service pointed at the right DTD itself.
  • Why can a DTD assertion fail on something an XSD assertion would ignore?
    The DTD path installs an error handler whose `warning`, `error` and `fatalError` methods all rethrow. A validating `DocumentBuilder` reports warnings for conditions a `Validator` would either ignore or never raise, so a warning during the second parse aborts the assertion exactly as an error would.

saying these in an interview costs you the question

  • Thinks matchesDtd validates the body exactly as received
  • Expects the response's own DOCTYPE to be honoured
  • Believes a DTD warning is tolerated during validation
  • Assumes matchesDtd accepts a resolver like matchesXsd
  • Writes a DTD whose root differs from the document element