XPath in Java and .NET
The JDK and the BCL both ship a complete XPath 1.0 engine: compile once, reuse the object, and configure namespaces before you evaluate anything.
Java: JAXP
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.*;
import java.util.Iterator;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
public class Query {
static final Map<String, String> NS = Map.of("c", "urn:example:catalog");
public static void main(String[] args) throws Exception {
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(new java.io.File("catalog.xml"));
XPath xp = XPathFactory.newInstance().newXPath();
xp.setNamespaceContext(new NamespaceContext() {
public String getNamespaceURI(String p) { return NS.getOrDefault(p, ""); }
public String getPrefix(String uri) { return null; }
public Iterator<String> getPrefixes(String uri) { return null; }
});
// Compile once when the expression is used in a loop
XPathExpression count = xp.compile("count(//c:book)");
System.out.println(count.evaluate(doc, XPathConstants.NUMBER));
XPathExpression ids = xp.compile("//c:book/@id");
NodeList nodes = (NodeList) ids.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(nodes.item(i).getNodeValue());
}
}
}XPathConstantshas exactly four values: NODESET, NODE, STRING, NUMBER and BOOLEAN.XPathExpressionis not thread-safe; keep one compiled object per thread or recompile inside the request.- Without a namespace context, any prefixed name in the expression throws
XPathExpressionException. - Large NODESETs hold the whole DOM in memory — for very large documents prefer streaming with StAX instead.
.NET: XPathDocument and XPathNavigator
using System.Xml;
using System.Xml.XPath;
var doc = new XPathDocument("catalog.xml");
var nav = doc.CreateNavigator();
var ns = new XmlNamespaceManager(nav.NameTable);
ns.AddNamespace("c", "urn:example:catalog");
// Evaluate a scalar
double count = (double)nav.Evaluate("count(//c:book)", ns);
// Select and read through the navigator, which is far cheaper than DOM nodes
foreach (XPathNavigator book in nav.Select("//c:book", ns))
{
Console.WriteLine(book.GetAttribute("id", ""));
Console.WriteLine(book.SelectSingleNode("title", ns)?.Value);
}
// Compile an expression that runs many times
var compiled = XPathExpression.Compile("//c:book[@id=$id]");
compiled.SetContext(ns);
var vars = new XsltArgumentList();
vars.AddParam("id", "", "B-1001");
foreach (XPathNavigator n in nav.Select(compiled, vars))
Console.WriteLine(n.OuterXml);| Task | Java | .NET |
|---|---|---|
| Load | DocumentBuilder | XPathDocument or XmlDocument |
| Namespace binding | setNamespaceContext | XmlNamespaceManager |
| Compile | XPath.compile | XPathExpression.Compile |
| Scalar result | XPathConstants.NUMBER | cast the Evaluate result |
| Variables | Set on the expression | XsltArgumentList |
| Streaming source | StAX + manual matching | XPathReader, or read forward |
⚠️
The classic .NET mistake is mixing
XmlDocument.SelectNodes (DOM, slower but writable) with XPathNavigator (read-only, fast) and expecting identical attribute handling: GetAttribute on the navigator takes the namespace URI as a second argument, and passing the wrong one silently returns null.FAQ
Is a compiled XPathExpression actually faster?
Yes for expressions evaluated in a loop, because parsing happens once. For a single evaluation the compile step costs more than it saves.
Which is better, XPathDocument or XmlDocument?
XPathDocument is optimised for read-only queries and is the right default. Use XmlDocument only when you also need to modify the tree.
Related
XPath in Python: lxml and ElementTree XPath 1.0, 2.0 and 3.1: what changed
Last refreshed 2026-09-18.