XMLUnit Core is a comprehensive XML testing library for Java that provides powerful tools for comparing XML documents, validating XML against schemas, and evaluating XPath expressions.
XPath 1.0 expression evaluation with namespace support, custom XPath functions, and various return type handling for node sets, strings, numbers, and booleans. XMLUnit provides a unified XPath engine interface with JAXP-based implementation.
The core interface for evaluating XPath expressions against XML documents.
public interface XPathEngine {
/** Evaluate XPath expression and return string result */
String evaluate(String xPath, Source s);
/** Evaluate XPath expression and return string result */
String evaluate(String xPath, Node n);
/** Select nodes matching XPath expression from Source */
Iterable<Node> selectNodes(String xPath, Source s);
/** Select nodes matching XPath expression from Node */
Iterable<Node> selectNodes(String xPath, Node n);
/** Configure namespace prefixes for XPath evaluation */
void setNamespaceContext(Map<String, String> prefix2Uri);
}The standard JAXP-based implementation of the XPath engine.
/**
* JAXP-based XPath engine implementation
* Uses javax.xml.xpath API for XPath 1.0 evaluation
*/
public class JAXPXPathEngine implements XPathEngine {
/** Create new XPath engine with default configuration */
public JAXPXPathEngine();
/** Configure XPath function resolver for custom functions */
public void setXPathFunctionResolver(XPathFunctionResolver resolver);
/** Configure XPath variable resolver for XPath variables */
public void setXPathVariableResolver(XPathVariableResolver resolver);
/** Configure namespace context for namespace-aware XPath */
@Override
public void setNamespaceContext(NamespaceContext ctx);
/** Configure namespace context using simple String mapping */
public void setNamespaceContext(Map<String, String> prefix2Uri);
}Usage Examples:
import org.xmlunit.xpath.JAXPXPathEngine;
import org.xmlunit.builder.Input;
import javax.xml.transform.Source;
import org.w3c.dom.Node;
// Basic XPath evaluation
JAXPXPathEngine xpath = new JAXPXPathEngine();
Source xmlSource = Input.fromString("""
<users>
<user id="1">
<name>John Doe</name>
<email>john@example.com</email>
<age>30</age>
</user>
<user id="2">
<name>Jane Smith</name>
<email>jane@example.com</email>
<age>25</age>
</user>
</users>
""").build();
// Evaluate XPath to get string result
String firstUserName = xpath.evaluate("/users/user[1]/name/text()", xmlSource);
System.out.println("First user: " + firstUserName); // "John Doe"
String userCount = xpath.evaluate("count(/users/user)", xmlSource);
System.out.println("User count: " + userCount); // "2"
// Select nodes matching XPath
Iterable<Node> userNodes = xpath.selectNodes("//user[@id]", xmlSource);
for (Node user : userNodes) {
String id = xpath.evaluate("@id", user);
String name = xpath.evaluate("name/text()", user);
System.out.println("User " + id + ": " + name);
}Configure namespace prefixes for namespace-aware XPath evaluation.
// Using Map<String, String> for simple namespace mapping
public void setNamespaceContext(Map<String, String> prefix2Uri);
// Using full NamespaceContext for advanced scenarios
public void setNamespaceContext(NamespaceContext ctx);Namespace Examples:
import org.xmlunit.xpath.JAXPXPathEngine;
import org.xmlunit.builder.Input;
import java.util.HashMap;
import java.util.Map;
// XML with namespaces
String namespacedXml = """
<?xml version="1.0"?>
<root xmlns:p="http://example.com/person" xmlns:a="http://example.com/address">
<p:person id="1">
<p:name>John Doe</p:name>
<a:address>
<a:street>123 Main St</a:street>
<a:city>Anytown</a:city>
</a:address>
</p:person>
</root>
""";
JAXPXPathEngine xpath = new JAXPXPathEngine();
// Configure namespace prefixes
Map<String, String> namespaces = new HashMap<>();
namespaces.put("person", "http://example.com/person");
namespaces.put("addr", "http://example.com/address");
xpath.setNamespaceContext(namespaces);
Source xmlSource = Input.fromString(namespacedXml).build();
// Use prefixes in XPath expressions
String personName = xpath.evaluate("//person:name/text()", xmlSource);
String city = xpath.evaluate("//addr:city/text()", xmlSource);
System.out.println("Name: " + personName); // "John Doe"
System.out.println("City: " + city); // "Anytown"
// Select nodes with namespaces
Iterable<Node> addressNodes = xpath.selectNodes("//addr:address", xmlSource);
for (Node address : addressNodes) {
String street = xpath.evaluate("addr:street/text()", address);
System.out.println("Street: " + street);
}Extend XPath evaluation with custom functions and variables.
/** Configure XPath function resolver for custom functions */
public void setXPathFunctionResolver(XPathFunctionResolver resolver);
/** Configure XPath variable resolver for XPath variables */
public void setXPathVariableResolver(XPathVariableResolver resolver);Custom Function Examples:
import javax.xml.xpath.XPathFunction;
import javax.xml.xpath.XPathFunctionResolver;
import javax.xml.namespace.QName;
// Custom XPath function
public class UpperCaseFunction implements XPathFunction {
@Override
public Object evaluate(List args) throws XPathFunctionException {
if (args.size() != 1) {
throw new XPathFunctionException("upper-case() requires exactly one argument");
}
return args.get(0).toString().toUpperCase();
}
}
// Function resolver
XPathFunctionResolver functionResolver = new XPathFunctionResolver() {
@Override
public XPathFunction resolveFunction(QName functionName, int arity) {
if ("upper-case".equals(functionName.getLocalPart()) && arity == 1) {
return new UpperCaseFunction();
}
return null;
}
};
JAXPXPathEngine xpath = new JAXPXPathEngine();
xpath.setXPathFunctionResolver(functionResolver);
// Use custom function in XPath
String upperName = xpath.evaluate("upper-case(/users/user[1]/name)", xmlSource);
System.out.println("Upper case name: " + upperName); // "JOHN DOE"Variable Examples:
import javax.xml.xpath.XPathVariableResolver;
import javax.xml.namespace.QName;
// Variable resolver
XPathVariableResolver variableResolver = new XPathVariableResolver() {
@Override
public Object resolveVariable(QName variableName) {
if ("minAge".equals(variableName.getLocalPart())) {
return 25;
}
return null;
}
};
JAXPXPathEngine xpath = new JAXPXPathEngine();
xpath.setXPathVariableResolver(variableResolver);
// Use variables in XPath
Iterable<Node> adults = xpath.selectNodes("//user[age >= $minAge]", xmlSource);
for (Node user : adults) {
String name = xpath.evaluate("name/text()", user);
String age = xpath.evaluate("age/text()", user);
System.out.println(name + " (age " + age + ")");
}Handle different XPath result types and evaluation patterns.
String Results:
// Text content
String text = xpath.evaluate("//user[1]/name/text()", xmlSource);
// Attribute values
String id = xpath.evaluate("//user[1]/@id", xmlSource);
// Concatenated results
String fullName = xpath.evaluate("concat(//user[1]/name, ' (', //user[1]/@id, ')')", xmlSource);
// Numeric results as strings
String count = xpath.evaluate("count(//user)", xmlSource);
String sum = xpath.evaluate("sum(//user/age)", xmlSource);Node Selection:
// Select multiple nodes
Iterable<Node> allUsers = xpath.selectNodes("//user", xmlSource);
// Select with predicates
Iterable<Node> adultUsers = xpath.selectNodes("//user[age >= 21]", xmlSource);
// Select attributes
Iterable<Node> userIds = xpath.selectNodes("//user/@id", xmlSource);
// Complex selections
Iterable<Node> specificUsers = xpath.selectNodes("//user[contains(name, 'John') or age > 25]", xmlSource);Boolean and Numeric XPath (evaluated as strings):
// Boolean expressions return "true" or "false"
String hasUsers = xpath.evaluate("count(//user) > 0", xmlSource);
String hasAdults = xpath.evaluate("//user[age >= 18]", xmlSource);
// Numeric expressions return string representations
String avgAge = xpath.evaluate("sum(//user/age) div count(//user)", xmlSource);
String maxAge = xpath.evaluate("//user[age = max(//user/age)]/age", xmlSource);Frequently used XPath patterns and examples for XML processing.
Element Selection:
// Root element
Node root = xpath.selectNodes("/*", xmlSource).iterator().next();
// All elements of specific type
Iterable<Node> users = xpath.selectNodes("//user", xmlSource);
// Elements by position
Node firstUser = xpath.selectNodes("//user[1]", xmlSource).iterator().next();
Node lastUser = xpath.selectNodes("//user[last()]", xmlSource).iterator().next();
// Elements by attribute
Iterable<Node> adminUsers = xpath.selectNodes("//user[@role='admin']", xmlSource);Attribute Access:
// All attributes of an element
Iterable<Node> userAttrs = xpath.selectNodes("//user[1]/@*", xmlSource);
// Specific attributes
String userId = xpath.evaluate("//user[1]/@id", xmlSource);
String userRole = xpath.evaluate("//user[@id='123']/@role", xmlSource);Text Content:
// Element text content
String userName = xpath.evaluate("//user[1]/name/text()", xmlSource);
// All text content (including descendants)
String allText = xpath.evaluate("//user[1]//text()", xmlSource);
// Normalized text content
String normalizedText = xpath.evaluate("normalize-space(//user[1]/description)", xmlSource);Conditional Selection:
// Existence checks
Iterable<Node> usersWithEmail = xpath.selectNodes("//user[email]", xmlSource);
Iterable<Node> usersWithoutEmail = xpath.selectNodes("//user[not(email)]", xmlSource);
// Value comparisons
Iterable<Node> youngUsers = xpath.selectNodes("//user[age < 25]", xmlSource);
Iterable<Node> seniorUsers = xpath.selectNodes("//user[age >= 65]", xmlSource);
// String matching
Iterable<Node> johnsAndJanes = xpath.selectNodes("//user[starts-with(name, 'J')]", xmlSource);
Iterable<Node> gmailUsers = xpath.selectNodes("//user[contains(email, 'gmail')]", xmlSource);Handle XPath evaluation errors and edge cases.
try {
JAXPXPathEngine xpath = new JAXPXPathEngine();
// Evaluate XPath expression
String result = xpath.evaluate("//user/name/text()", xmlSource);
// Handle empty results
if (result == null || result.isEmpty()) {
System.out.println("No matching elements found");
} else {
System.out.println("Result: " + result);
}
} catch (RuntimeException e) {
// Handle XPath syntax errors
if (e.getCause() instanceof XPathExpressionException) {
System.err.println("Invalid XPath expression: " + e.getMessage());
} else {
System.err.println("XPath evaluation error: " + e.getMessage());
}
}
// Check node iteration results
Iterable<Node> nodes = xpath.selectNodes("//nonexistent", xmlSource);
boolean hasResults = nodes.iterator().hasNext();
if (!hasResults) {
System.out.println("XPath returned no nodes");
}XPath evaluation integrates with XMLUnit's input, comparison, and validation features.
// Use with different input sources
Source fileSource = Input.fromFile("data.xml").build();
Source jaxbSource = Input.fromJaxb(personObject).build();
Source transformedSource = Transform.source(originalXml)
.usingStylesheet(stylesheetSource)
.build();
// Evaluate XPath on all source types
String fileResult = xpath.evaluate("//name/text()", fileSource);
String jaxbResult = xpath.evaluate("//name/text()", jaxbSource);
String transformResult = xpath.evaluate("//name/text()", transformedSource);
// Use XPath in custom difference evaluators
DifferenceEvaluator xpathIgnore = (comparison, outcome) -> {
// Ignore differences in elements matching specific XPath
if (xpath.selectNodes("//timestamp", comparison.getControlDetails().getTarget()).iterator().hasNext()) {
return ComparisonResult.SIMILAR;
}
return outcome;
};
// Use XPath with namespace context from validation
Map<String, String> validationNamespaces = new HashMap<>();
validationNamespaces.put("ns", "http://example.com/namespace");
xpath.setNamespaceContext(validationNamespaces);
String namespacedResult = xpath.evaluate("//ns:element/text()", xmlSource);Install with Tessl CLI
npx tessl i tessl/maven-org-xmlunit--xmlunit-core