Executing Metapath
This guide explains how to use Metapath expressions to query OSCAL documents programmatically.
Metapath is an expression language for querying Metaschema-based documents. It's similar to XPath but works across all formats (XML, JSON, YAML).
import dev.metaschema.oscal.lib.OscalBindingContext;
import dev.metaschema.oscal.lib.model.Catalog;
import dev.metaschema.core.metapath.MetapathExpression;
import dev.metaschema.core.metapath.IMetapathExpression;
import dev.metaschema.core.metapath.item.IItem;
import dev.metaschema.core.metapath.item.ISequence;
import dev.metaschema.databind.io.Format;
import dev.metaschema.databind.io.IDeserializer;
import java.nio.file.Path;
OscalBindingContext context = OscalBindingContext.instance();
// Load a catalog
IDeserializer<Catalog> deserializer = context.newDeserializer(
Format.JSON, Catalog.class);
Catalog catalog = deserializer.deserialize(Path.of("catalog.json"));
// Compile an expression
IMetapathExpression expression = MetapathExpression.compile(
"//control", context.getStaticContext());
// Evaluate against the catalog
ISequence<?> results = expression.evaluate(catalog);
// Process results
for (IItem item : results) {
System.out.println("Found: " + item.toAtomicItem().asString());
}
IMetapathExpression expr = MetapathExpression.compile(
"//control", context.getStaticContext());
ISequence<?> controls = expr.evaluate(catalog);
IMetapathExpression expr = MetapathExpression.compile(
"//control[@id='ac-1']", context.getStaticContext());
ISequence<?> result = expr.evaluate(catalog);
IMetapathExpression expr = MetapathExpression.compile(
"//control/title", context.getStaticContext());
ISequence<?> titles = expr.evaluate(catalog);
titles.forEach(title ->
System.out.println(title.toAtomicItem().asString()));
IMetapathExpression expr = MetapathExpression.compile(
"count(//control)", context.getStaticContext());
ISequence<?> result = expr.evaluate(catalog);
int count = result.getFirstItem(true)
.toAtomicItem()
.asInteger()
.intValue();
System.out.println("Control count: " + count);
// Find controls with status=withdrawn
IMetapathExpression expr = MetapathExpression.compile(
"//control[prop[@name='status'][@value='withdrawn']]",
context.getStaticContext());
import dev.metaschema.databind.model.IBoundObject;
import dev.metaschema.oscal.lib.model.Control;
ISequence<?> results = expression.evaluate(catalog);
for (IItem item : results) {
if (item instanceof IBoundObject) {
Object value = ((IBoundObject) item).getValue();
if (value instanceof Control) {
Control control = (Control) value;
System.out.println("Control ID: " + control.getId());
System.out.println("Title: " + control.getTitle());
}
}
}
IMetapathExpression expr = MetapathExpression.compile(
"//control/@id", context.getStaticContext());
ISequence<?> ids = expr.evaluate(catalog);
ids.forEach(item -> {
String id = item.toAtomicItem().asString();
System.out.println("ID: " + id);
});
liboscal-java provides OSCAL-specific Metapath functions:
Check if a property has an OSCAL namespace:
IMetapathExpression expr = MetapathExpression.compile(
"//prop[has-oscal-namespace(., 'https://fedramp.gov/ns/oscal')]",
context.getStaticContext());
Resolve a profile import (within Metapath):
IMetapathExpression expr = MetapathExpression.compile(
"resolve-profile(import)",
context.getStaticContext());
// All control groups
"//group"
// Controls in a specific group
"//group[@id='access-control']/control"
// Control enhancements
"//control[contains(@id, '.')]"
// All parameters
"//param"
// All imports
"//import"
// Included controls
"//import/include-controls/with-id"
// Parameter modifications
"//modify/set-parameter"
// System name
"/system-security-plan/system-characteristics/system-name"
// Implemented requirements
"//implemented-requirement"
// Components
"//component"
import dev.metaschema.core.metapath.MetapathException;
try {
IMetapathExpression expr = MetapathExpression.compile(
"//control[invalid syntax", context.getStaticContext());
} catch (MetapathException e) {
System.err.println("Invalid expression: " + e.getMessage());
}
For repeated queries, cache compiled expressions:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MetapathQueryCache {
private final Map<String, IMetapathExpression> cache =
new ConcurrentHashMap<>();
private final StaticContext staticContext;
public MetapathQueryCache(OscalBindingContext context) {
this.staticContext = context.getStaticContext();
}
public IMetapathExpression getExpression(String path) {
return cache.computeIfAbsent(path, p ->
MetapathExpression.compile(p, staticContext));
}
public ISequence<?> query(Object document, String path) {
return getExpression(path).evaluate(document);
}
}
| Expression | Description |
|---|---|
/catalog |
Root catalog |
//control |
All controls at any depth |
control[@id='ac-1'] |
Control with specific ID |
control[1] |
First control |
control[last()] |
Last control |
control/title |
Titles of direct child controls |
.. |
Parent element |
. |
Current element |
| Function | Description |
|---|---|
count(seq) |
Count items |
string-length(str) |
String length |
starts-with(str, prefix) |
Check prefix |
contains(str, substr) |
Check substring |
not(expr) |
Logical negation |
exists(seq) |
Check if non-empty |
- Compile once, use many - Cache compiled expressions
- Be specific - Narrow paths are faster than
// - Handle empty results - Check sequence length before accessing
- Use predicates - Filter in the expression, not in Java
- Test expressions - Validate with CLI before embedding
Continue learning about liboscal-java with these related guides:
- Validating with Constraints - Validate queried data
- Reading & Writing Data - Load documents to query
- Architecture - Understand Metapath internals

