Path Namespaces - Query Configuration

Namespace Handling in Path Expressions

personClever Llamas
CleverLlamasMinimum Llamaverse Version: 2.3.1
databaseMinimum MarkLogic Version: 8

Namespaces are one of the quietest ways to get completely wrong answers in MarkLogic — or more precisely, to get no answers at all. A query that returns an empty sequence when you know the data is there is disorienting. The database is not broken. The document is not missing. The only thing wrong is the expanded name, and that one character-level mismatch produces no error and no match.

It is an impressively subtle failure mode: technically correct, operationally annoying.

This article explains why namespace handling works the way it does, how to write queries and configure path range indexes correctly, and how to diagnose mismatches systematically when they occur.

XML Namespace Fundamentals

An XML namespace is a URI. It is not the prefix you see in the document, and it is not the element's local name. The real identity of an element is its expanded name: the combination of its local name and its namespace URI. Two elements with the same local name but different URIs are different elements. Two elements with different prefixes but the same URI are the same element.

There are three distinct namespace states an XML element can be in. The table below summarises them.

StateWhat it looks likeWhat it means
Null namespace<name>Marisol</name> with no xmlns in scopeThe element has no namespace URI — it is in the null namespace
Default namespace<llama-profile xmlns="http://example.com/llamas">All unprefixed child elements inherit the namespace URI silently
Prefix-bound namespace<ll:llama-profile xmlns:ll="http://example.com/llamas">The element and its children are explicitly namespaced using a prefix

The most common source of confusion is the default namespace case. When a document uses xmlns="http://example.com/llamas", every unprefixed element in scope — including <name>, <id>, and <height-cm> — is in the http://example.com/llamas namespace. The document shows no prefixes, but the elements are fully namespaced. A query that does not account for this will silently return nothing.

To illustrate, here is the XML document used throughout this article:

<llama-profile xmlns="http://example.com/llamas">
  <id>llama-001</id>
  <name>Marisol</name>
  <height-cm>118</height-cm>
  <habitat>
    <region>Connemara</region>
  </habitat>
</llama-profile>

The Silent Failure Pattern

Given that document, the most natural-looking XPath fails completely:

xquery version "1.0-ml";

(: No namespace declaration in this query.                                    :)
(: The document uses a default namespace, so all elements are namespaced.     :)
(: The path /llama-profile/name targets null-namespace elements — no match.  :)

let $doc := doc('/cleverllamas/llamaverse/extensions/path-namespaces/llama-profile.xml')
return $doc/llama-profile/name
()

The path /llama-profile/name targets elements in the null namespace. Because the document uses a default namespace, none of its elements are in the null namespace. MarkLogic returns an empty sequence and reports no error. This is not a bug — it is precise and correct behaviour. It is just not what most people expect the first time they encounter it.

Declaring Namespaces in XQuery

XQuery gives you two ways to declare namespace intent in a module prolog.

Explicit prefix declaration

declare namespace ll = "http://example.com/llamas"; binds the prefix ll to the namespace URI for the duration of the module. You then use ll: on every element step in your path expressions.

xquery version "1.0-ml";
declare namespace ll = "http://example.com/llamas";

(: Bind the prefix ll to the namespace URI used by the document.              :)
(: Now the path /ll:llama-profile/ll:name resolves to the correct element.   :)

let $doc := doc('/cleverllamas/llamaverse/extensions/path-namespaces/llama-profile.xml')
return $doc/ll:llama-profile/ll:name
<name xmlns="http://example.com/llamas">Marisol</name>

The prefix itself is arbitrary — ll, ns, lp, or anything else will work provided it resolves to the same URI as the document. The URI is the identity; the prefix is a label.

Default element namespace declaration

declare default element namespace "http://example.com/llamas"; applies the given URI to every unprefixed element step in the module. You can then write /llama-profile/name without any prefix.

xquery version "1.0-ml";
declare default element namespace "http://example.com/llamas";

(: declare default element namespace applies the given URI to every           :)
(: unprefixed element step in this module. The path /llama-profile/name      :)
(: now resolves to the namespaced elements — no prefix is needed.            :)

let $doc := doc('/cleverllamas/llamaverse/extensions/path-namespaces/llama-profile.xml')
return $doc/llama-profile/name
<name xmlns="http://example.com/llamas">Marisol</name>

This style is convenient for modules that query exclusively namespaced XML, but it creates a trap: if the module also accesses null-namespace elements, those paths will unexpectedly fail because the default namespace is now applied to them too. For shared application code and index definitions, explicit prefixes are generally safer.

The table below summarises both approaches.

ApproachProlog declarationPath styleBest suited to
Explicit prefixdeclare namespace ll = "..."/ll:root/ll:childShared modules, index queries
Default element namespacedeclare default element namespace "..."/root/childSingle-namespace modules

Declaring Namespaces in Server-Side JavaScript

Server-Side JavaScript does not support declare namespace as a module-level statement. Instead, namespace declarations go directly inside the XPath string, at the start of the expression. This keeps the namespace binding local to the path evaluation and makes the path self-contained.

Inspecting the namespace of a stored node

Before writing any XPath, use fn.namespaceURI() and fn.localName() to confirm what namespace a stored element actually carries:

'use strict';

// fn.namespaceURI() and fn.localName() inspect the expanded name of a node.
// Use these to prove what namespace a stored XML element actually carries.

const doc = fn.doc('/cleverllamas/llamaverse/extensions/path-namespaces/llama-profile.xml');
const parts = Array.from(xdmp.xqueryEval(
  'xquery version "1.0-ml"; declare variable $d external; (fn:local-name($d/*), fn:namespace-uri($d/*))',
  { d: doc },
  { update: 'false' }
));

({
  localName: fn.string(parts[0]),
  namespaceURI: fn.string(parts[1])
});
{
  "localName": "llama-profile",
  "namespaceURI": "http://example.com/llamas"
}

A non-empty namespaceURI tells you immediately that you must account for a namespace in any XPath against this document.

XPath with an inline namespace declaration

'use strict';

// In Server-Side JavaScript, namespace declarations go inside the XPath string.
// The declare namespace clause must appear at the start of the expression.

const doc = fn.doc('/cleverllamas/llamaverse/extensions/path-namespaces/llama-profile.xml');
const query = 'xquery version "1.0-ml"; declare namespace ll="http://example.com/llamas"; declare variable $d external; string($d/ll:llama-profile/ll:name)';

fn.string(fn.head(xdmp.xqueryEval(
	query,
	{ d: doc },
	{ update: 'false' }
)));
Marisol

The declare namespace ll="..."; clause at the start of the path string is the JavaScript equivalent of declare namespace in XQuery. The namespace must be inside the string because doc.xpath() evaluates the path in its own expression context.

Path Range Indexes and Namespaces

Path range indexes do not have their own namespace system. They use path expressions — and path expressions use expanded names. That means every rule that applies to XPath queries applies equally to path range index definitions.

A path range index configured with the path /llama-profile/name targets null-namespace elements. If the documents use a default namespace, the index builds successfully, stores no data, and returns nothing when queried. This is the same silent failure pattern, but it can cost hours to diagnose because the Admin UI gives no indication that the configuration is semantically wrong.

What the Admin UI Fields Really Mean

When you configure a path range index in the Admin UI, you provide two logically related things:

  1. A path expression, such as /ll:llama-profile/ll:height-cm
  2. Namespace bindings that assign a URI to each prefix used in that path

The prefix in the path expression is just a label. The namespace binding field gives that label meaning. If the URI is wrong, the index will not match the intended documents. If the prefix used in the path is missing from the namespace bindings, the path expression is incomplete.

Admin UI fieldExample valueWhy it matters
Path Expression/ll:llama-profile/ll:height-cmThe path MarkLogic indexes — must use correct expanded names
Namespace PrefixllThe prefix used in the path expression
Namespace URIhttp://example.com/llamasMust match the document namespace exactly
Scalar TypeintDetermines how values are indexed and queried
Collatione.g. http://marklogic.com/collation/Required for string types; controls comparison behaviour

A working index configuration for the llama-profile.xml document above looks like this:

  • Path expression: /ll:llama-profile/ll:height-cm
  • Namespace prefix: ll
  • Namespace URI: http://example.com/llamas
  • Scalar type: int

Querying a Path Range Index From XQuery

When querying a path range index, the path and namespace bindings in the query must agree with those in the index definition. The cts:path-range-query() function accepts a namespace bindings map as its seventh argument:

xquery version "1.0-ml";

(: The path expression and the namespace binding map must agree.              :)
(: The path uses ll: as a prefix; the map binds ll to the same URI           :)
(: that the document uses. Any prefix works, as long as the URI matches.     :)
declare namespace ll = "http://example.com/llamas";

let $query := cts:path-range-query(
  "/ll:llama-profile/ll:height-cm",
  ">=",
  115
)
for $doc in cts:search(fn:collection(), $query)
return xdmp:node-uri($doc)
/cleverllamas/llamaverse/extensions/path-namespaces/marisol.xml
/cleverllamas/llamaverse/extensions/path-namespaces/solana.xml

The URI in the map must match the URI used when the index was created. The prefix can differ between the index definition and the query — again, only the URI matters for matching.

Descendant Paths and Namespaces

A common misconception is that //element relaxes namespace rules because it broadens the structural search. It does not. //element is short for /descendant-or-self::node()/child::element(element), and the name test still resolves by expanded name. //region still targets null-namespace region elements; //ll:region targets region elements in the http://example.com/llamas namespace.

xquery version "1.0-ml";
declare namespace ll = "http://example.com/llamas";

(: Comparing anchored vs descendant path expressions.                        :)
(: Neither broadens namespace rules — both require the correct expanded name. :)

let $doc :=
  <llama-profile xmlns="http://example.com/llamas">
    <habitat>
      <region>Connemara</region>
    </habitat>
  </llama-profile>
return (
  $doc//region,           (: targets null namespace — no match :)
  $doc//ll:region,        (: targets http://example.com/llamas — matches :)
  $doc/ll:habitat/ll:region (: wrong path: habitat is not the document root :)
)
()
<region xmlns="http://example.com/llamas">Connemara</region>
()

Being structurally broader than an anchored path does not make a descendant path namespace-broader. Both require the correct expanded name at every step.

Diagnosing Namespace Mismatches Systematically

When a path query returns nothing and you are confident the data is there, work through this checklist before touching the query or the index:

  1. Inspect the raw stored document — use fn:doc() or Query Console against the actual URI, not a transformed copy from application code.
  2. Run fn:namespace-uri() and fn:local-name() on the target element — prove what namespace and local name the stored node actually carries.
  3. Compare the namespace URI character by character against the Admin UI binding and the query map — http vs https, trailing slashes, and case differences are all invisible to the naked eye but cause total mismatches.
  4. Test a plain XPath against the document before testing a cts:path-range-query() — if the XPath fails, the indexed query will fail for the same reason.
  5. Check collation and scalar type only after the namespace is confirmed.

The diagnostic script below automates the first few checks:

xquery version "1.0-ml";
declare namespace ll = "http://example.com/llamas";

(: Diagnostic: use this script to prove what namespace a stored document    :)
(: actually carries before debugging a path range index query.              :)

let $doc := fn:doc('/cleverllamas/llamaverse/extensions/path-namespaces/llama-profile.xml')
return map:new((
  map:entry('root-local-name',         fn:local-name($doc/*)),
  map:entry('root-namespace',          fn:namespace-uri($doc/*)),
  map:entry('name-exists-unprefixed',  fn:exists($doc/llama-profile/name)),
  map:entry('name-exists-prefixed',    fn:exists($doc/ll:llama-profile/ll:name)),
  map:entry('name-value',              fn:string($doc/ll:llama-profile/ll:name))
))
{
  "root-local-name": "llama-profile",
  "root-namespace": "http://example.com/llamas",
  "name-exists-unprefixed": false,
  "name-exists-prefixed": true,
  "name-value": "Marisol"
}

The name-exists-unprefixed: false and name-exists-prefixed: true output makes the mismatch explicit at a glance. Run this whenever a stored document is not behaving as expected.

The four most common failure modes worth memorising are:

Failure modeSymptomFix
Default namespace, path omits prefixEmpty resultsDeclare a prefix or default element namespace in the query
Path range index uses unprefixed path for namespaced elementsIndex builds; never matchesReconfigure index with namespace-qualified path
Query prefix bound to wrong URIEmpty resultsCorrect the URI in the namespace map
URI differs subtly (http vs https, trailing slash)Empty resultsCopy URI directly from a verified source

JSON Documents and the Namespace-Free Lane

JSON property names are not XML element names. They carry no namespace URI and no inheritance from parent nodes. When you query JSON with cts:json-property-value-query() or cts:json-property-range-query(), you pass the property name as a plain string — no namespace prefix is involved.

The llamaverse stores llama data as JSON documents in the wild-llamas collection. A query for llamas named Aaron requires no namespace handling at all:

xquery version "1.0-ml";

(: JSON property queries do not use XML namespace declarations.              :)
(: Property names are strings, not XML expanded names.                      :)
(: This query finds all wild llamas named Aaron — no prefix needed.         :)

for $doc in cts:search(
  fn:collection("wild-llamas"),
  cts:json-property-value-query("name", "Aaron")
)[1 to 3]
return xdmp:node-uri($doc)
/cleverllamas/llamaverse/raw/wild-llamas/llamas/8ee6e559-6460-489e-8f30-9366f9bbf41c.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/7b1de50d-4d0d-43eb-9cfb-2643dc4b9bc5.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/d4fa7481-4044-41f6-979b-cfe106aae806.json

The simplicity here is real, not incidental. It reflects the fact that JSON has a different data model. The absence of namespace declarations is not a shortcut — it is accurate.

The examples in this article assume the llamaverse (v2.3.1+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse — see the llamaverse article for full setup instructions.

Mixed XML and JSON Databases

Many production MarkLogic databases contain both XML and JSON. The important discipline is to keep the mental models separate: XML namespace rules apply to XML; JSON property rules apply to JSON. Carrying XML namespace declarations into a JSON property query accomplishes nothing. Assuming JSON property names behave like XML local names leads to confusion when the same prefix handling that works for XML has no effect on JSON.

Data formatTypical index typeName resolution ruleCommon pitfall
XMLPath range indexExpanded name including namespace URIDefault namespace treated as if no namespace
JSONJSON property or property range indexProperty name exactly as storedAssuming XML namespace declarations apply
Mixed databaseBoth, separatelyEach query type follows its own modelReusing XML path habits in JSON code

Quick Reference

XQuery vs JavaScript namespace syntax

TaskXQueryServer-Side JavaScript
Declare a prefixdeclare namespace ll = "http://example.com/llamas";Include declare namespace ll="..."; inside the XPath string
Default element namespacedeclare default element namespace "...";Not available as a module declaration; use inline prefix declarations in XPath strings
Inspect element namespacefn:namespace-uri($node)fn.namespaceURI(node)
Inspect local namefn:local-name($node)fn.localName(node)
Debug a nodexdmp:describe($node)xdmp.describe(node)

Common mistakes

MistakeWhat happensFix
Querying a default-namespaced document without a namespace declarationEmpty resultsDeclare a prefix and use /ns:root/ns:child, or declare a default element namespace
Configuring a path range index with an unprefixed path for namespaced XMLIndex builds but never matchesReconfigure with the correct namespace-qualified path
Binding the right prefix to the wrong URIEmpty resultsCompare the URI character by character with namespace-uri() output
Assuming the document prefix must match the query prefixUnnecessary rewritesAny prefix works; only the URI must match
Using XML namespace declarations in JSON property queriesQueries do not behave as intendedUse JSON property names directly — no prefix needed
Assuming //element ignores namespacesEmpty results from descendant queriesDeclare and use the correct namespace for descendant steps too

FAQ

Does the query prefix have to match the document prefix?

No. Only the namespace URI must match. You can use any prefix you like in a query, as long as you bind it to the same URI the document uses.

If the XML shows no prefixes, is it unnamespaced?

Not necessarily. A default namespace declaration silently namespaces every descendant element even when no prefixes are visible. Run fn:namespace-uri() against a stored node to be certain.

Does //name ignore namespaces?

No. It broadens the structural search — descendants rather than direct children — but does not relax namespace rules. //name still targets null-namespace name elements.

Why did my path range index build successfully if the path is wrong?

Because the index definition is syntactically valid. MarkLogic validates that the path expression and namespace bindings are well-formed, not that they match any documents in the database.

What is the fastest first diagnostic step?

Run fn:namespace-uri() against the actual stored node. If it returns a non-empty string, you must account for that URI in every path expression targeting that element.

Do this first, and you usually save yourself an hour of creative but unhelpful troubleshooting.

Need Some Help?


Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!