skip to content

In REST Assured, why does an XML path with a namespace prefix return an empty string?

level: seniorimportance: must knowfreq 45%

answer

  1. the empty string is the tell
  2. binding is by URI, not prefix
  3. namespaceAware already defaults to true
  4. unprefixed steps are namespace-blind
  5. hasXPath needs its own NamespaceContext

basics

~20 s

REST Assured resolves a path prefix through XmlConfig's declared namespaces, not the document's. Until declareNamespace(prefix, uri) binds that exact URI, a prefixed step matches nothing and reads back as an empty string; setting namespaceAware alone does not help.

solid answer

~50 s

The prefix in a GPath step is resolved against the namespaces **you** declared, not the ones the document happens to use. REST Assured hands the parsed tree a map built from `XmlConfig.declaredNamespaces()`, so `sprayDiary.agro:product` resolves to nothing — and `text()` on nothing is `""` — until you call `xmlConfig().declareNamespace("agro", "http://orchard.example/agrochem")` with that exact URI. The binding is by URI, so your prefix need not match the document's: declare `chem` for the same URI and write `sprayDiary.chem:product`. `namespaceAware(boolean)` already defaults to `true`, so turning it on fixes nothing on its own; `declareNamespace(...)` sets it as a side effect anyway. An unprefixed step matches by local name in any namespace, and a leading colon (`:sprayDiary.:product`) restricts it to the element in no namespace. Nothing is raised along the way, so the assertion just compares against `""` — that empty string is the tell.

code

java · 22 lines
java
import static io.restassured.RestAssured.given;
import static io.restassured.config.RestAssuredConfig.newConfig;
import static io.restassured.config.XmlConfig.xmlConfig;
import static org.hamcrest.Matchers.equalTo;

// <sprayDiary xmlns:agro="http://orchard.example/agrochem">
//   <product>sulphur</product>
//   <agro:product>captan</agro:product>
// </sprayDiary>

given()
    .config(newConfig().xmlConfig(
        xmlConfig().declareNamespace("chem", "http://orchard.example/agrochem")))
.when()
    .get("/blocks/spray-diary")
.then()
    // no prefix: every product element, concatenated
    .body("sprayDiary.product.text()", equalTo("sulphurcaptan"))
    // leading colon: only the element in no namespace
    .body(":sprayDiary.:product.text()", equalTo("sulphur"))
    // your prefix for that URI - the document says agro:, the path says chem:
    .body("sprayDiary.chem:product.text()", equalTo("captan"));

go deeper

for a junior

Recall that a prefixed step needs XmlConfig.declareNamespace(prefix, uri) before it resolves, and that the failure looks like an empty string rather than an error.

for a middle

Explain the resolution mechanism: the map REST Assured declares on the parsed tree comes from XmlConfig, binding is by URI, and an unprefixed step matches by local name regardless of namespace.

for a senior

Diagnose the silent-empty case on a real SOAP-era endpoint, decide when a declaration is genuinely needed versus when local-name matching suffices, and spot the hasXPath channel as a separate configuration surface.

for a principal

Own the convention for a suite that spans namespaced and unnamespaced payloads: where declarations live, whether they belong in a shared spec, and how you keep a silent empty match from ever passing as a green test.

## The symptom You point a path at a namespaced element in a spray-diary response and the assertion fails against `""`, not against the value you can plainly read in the body. Nothing throws, nothing warns, and the raw body looks exactly like what you asked for. ```xml <sprayDiary xmlns:agro="http://orchard.example/agrochem"> <product>sulphur</product> <agro:product>captan</agro:product> </sprayDiary> ``` `then().body("sprayDiary.agro:product.text()", equalTo("captan"))` fails, and the reported actual value is the empty string. ## The prefix in your path is not the prefix in the document REST Assured parses XML with Groovy's `XmlSlurper` and then declares a namespace map on the result — a map built from `XmlConfig.declaredNamespaces()`. A prefixed step is resolved through **that** map. If the prefix is absent from it, the step matches no node, and `text()` over no nodes is the empty string. Two consequences are worth internalising: - **The prefix is yours to choose.** Declare `declareNamespace("chem", "http://orchard.example/agrochem")` and the working path is `sprayDiary.chem:product`, even though the document writes `agro:`. The binding key is the URI. - **A wrong URI behaves exactly like no declaration.** Bind `agro` to `http://orchard.example/agrochem/` with a trailing slash and you are back to the empty string, with no clue that you were one character out. ## The three path shapes | Path | What it selects | Result on the body above | |---|---|---| | `sprayDiary.product.text()` | every `product` by local name, whatever its namespace | `sulphurcaptan` | | `:sprayDiary.:product.text()` | the leading colon restricts each step to no namespace | `sulphur` | | `sprayDiary.chem:product.text()` | the `product` in the URI bound to `chem` | `captan` | The first row is the one people miss: an unprefixed step is namespace-blind, so it matches both elements and `text()` concatenates them with no separator. That behaviour is not a bug to work around; it is the default that makes most namespaced payloads readable with no configuration at all, and it is why the empty string, not a concatenation, is the signal that a *prefixed* step failed to bind. ## Why SOAP bodies usually need no declarations at all That namespace-blindness is why a SOAP envelope reads cleanly with nothing configured. `Envelope.Body.importProjectResponse.ProjectImportResultCode.code` walks straight through `env:`, `n1:` and `n2:` prefixes without a single declaration, because every step matches on local name. You only need `declareNamespace` when you must **disambiguate** — when two elements share a local name and differ only by namespace, as `product` does above. ## `namespaceAware` is not the switch you think it is - `XmlConfig.namespaceAware(boolean)` already defaults to `true`, so "turning it on" changes nothing. - Setting `namespaceAware(true)` on its own still leaves a prefixed path resolving to `""`, because no prefix has been bound to anything. - `declareNamespace(prefix, uri)` forces `namespaceAware` to `true` as a side effect, so you rarely touch the flag directly. - `declareNamespaces(Map)` sets it to `true` when the map is non-empty, and leaves the current value alone when the map is null. - The declarations propagate onto an extracted path object: REST Assured copies `XmlConfig`'s declared namespaces, features and properties into the `XmlPathConfig` behind a response's `xmlPath()`, so the same prefixed paths work there. - If you build an `XmlPath` by hand instead, the equivalent call lives on `XmlPathConfig` and is spelled `declaredNamespace(prefix, uri)` — past tense, and an easy mis-type against `XmlConfig.declareNamespace(...)`. ## Hamcrest's `hasXPath` has a second, separate namespace channel Namespaces declared in `XmlConfig` do **not** reach the `hasXPath` matcher — REST Assured says exactly that in the Javadoc of both `declareNamespace` and `declareNamespaces`. `org.hamcrest.xml.HasXPath` takes its namespaces as a `javax.xml.namespace.NamespaceContext` argument, across four overloads: `hasXPath(String)`, `hasXPath(String, Matcher<String>)`, `hasXPath(String, NamespaceContext)` and `hasXPath(String, NamespaceContext, Matcher<String>)`. Having two independent namespace channels inside one assertion chain is a real trap: a suite that declares its namespaces once in `XmlConfig` and then reaches for `hasXPath` for one awkward assertion will find that assertion silently namespace-blind. ## Diagnosing it in five steps 1. Log or extract the raw body and note the namespace **URI**, not the prefix the server chose. 2. Declare it with a prefix of your own and that exact URI, via `given().config(newConfig().xmlConfig(xmlConfig().declareNamespace(...)))`. 3. Re-run. If the value is still empty, compare the URIs character by character — trailing slashes and `http` versus `https` both count. 4. If what you actually wanted was the element that is in *no* namespace, drop the declaration and use the leading-colon form instead. 5. If the failing assertion is a `hasXPath`, none of the above applies: pass it a `NamespaceContext`. The wider lesson is that an empty string is never proof of an empty element here. It is equally the shape of a step that matched nothing, so a suite that asserts `not(emptyString())` on namespaced XML is asserting something weaker than it looks.

  • Your namespace is declared and the DSL assertion passes, but the same path on the extracted XmlPath is empty. What would you check?
    Whether the config was on the request at all. REST Assured copies `XmlConfig`'s declared namespaces, features and properties into the `XmlPathConfig` behind `xmlPath()`, so a path object taken from that response inherits them — but an `XmlPath` you construct yourself from a raw string does not. Give that one `XmlPathConfig.declaredNamespace(prefix, uri)`.
  • Why can a hasXPath assertion still be namespace-blind after you declared the namespace in XmlConfig?
    Because `hasXPath` is Hamcrest's matcher over a DOM node and takes its own `javax.xml.namespace.NamespaceContext`. REST Assured's Javadoc states outright that `declareNamespace` cannot add namespaces for it. Use the `hasXPath(String, NamespaceContext)` or `hasXPath(String, NamespaceContext, Matcher)` overload for those assertions.
  • When do you genuinely need declareNamespace at all?
    Only to disambiguate. An unprefixed GPath step matches by local name in any namespace, which is why a SOAP envelope reads fine with nothing declared. You need a declaration when two elements share a local name and differ only by namespace, or when you must assert on precisely the one in no namespace.

A namespace prefix is a local nickname, the way an import alias is: the document may call it agro and your path may call it chem, because what actually identifies the thing is the URI behind the nickname.

saying these in an interview costs you the question

  • Thinks the path prefix must match the document's prefix
  • Sets namespaceAware(true) and expects prefixed paths to resolve
  • Assumes an undeclared prefix raises rather than yielding an empty string
  • Believes every namespaced document needs a declaration to be read
  • Expects XmlConfig namespaces to reach the hasXPath matcher
  • Treats an unprefixed step as matching only the default namespace