A native JSONata implementation for Java 21. Parse expressions once, evaluate them against JSON payloads, and get typed results — no embedded JavaScript engine required.
JSON is everywhere, but working with nested or variable-shaped documents in Java often means verbose Jackson code: loops, null checks, intermediate DTOs, and glue that obscures the actual transformation.
JSONata is a declarative query and transformation language for JSON. Think XPath for JSON: you describe what you want from the data, not how to walk the tree.
| Without JSONata | With JSONata |
|---|---|
| Write navigation, filtering, and mapping in Java | Express the same logic in one compact expression |
| Change the output shape → refactor Java code | Change the expression string |
| Reuse logic across services → share Java classes | Reuse the same expression in Java, config files, or other JSONata runtimes |
- API integration — reshape vendor JSON into your domain model
- Event processing — extract fields, filter arrays, compute aggregates from payloads
- Configuration and rules — keep transformation logic in data-driven expressions instead of hard-coded Java
- ETL and reporting — project, group, and summarize JSON documents without a separate tool chain
- Native Java — ANTLR parser and Java evaluator; no Node.js or Nashorn for core evaluation
- Familiar API — compile with
JSONata.jsonata(expr), evaluate with.evaluate(json) - Extensible — bind variables and register custom functions from Java
- Spec-aligned — validated against the jsonata-js test suite
- Java 21+
- Maven 3.x (to build from source)
The library is not yet published to Maven Central. Build and install locally:
git clone https://github.com/vepo/jsonata.java.git
cd jsonata.java
git submodule update --init --recursive
mvn installThen add the dependency to your project:
<dependency>
<groupId>dev.vepo</groupId>
<artifactId>jsonata.java</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>The module name is jsonata.java (JPMS). Only dev.vepo.jsonata is exported.
The typical workflow is compile once, evaluate many times:
import static dev.vepo.jsonata.JSONata.jsonata;
var expression = jsonata("Address.City");
var result = expression.evaluate("""
{
"FirstName": "Fred",
"Address": { "City": "Winchester" }
}
""");
result.asText(); // "Winchester"JSONata.jsonata(String) parses the expression and returns a reusable evaluator. Pass a JSON string to evaluate; the root document becomes the expression context ($).
evaluate returns a JSONataResult. Use typed accessors for scalar values:
result.asText(); // string
result.asInt(); // integer
result.asDouble(); // floating point
result.asBoolean(); // boolean
result.isNull(); // JSON null
result.isEmpty(); // no match (JSONata "undefined")When an expression produces a sequence (for example, filtering an array), use multi():
var emails = jsonata("Phone[type='mobile'].number").evaluate(json);
emails.multi().asText(); // List.of("077 7700 1234")For complex output (objects, arrays), asText() returns a JSON string:
var grouped = jsonata("""
Account.Order.Product {
`Product Name`: $sum($.(Price * Quantity))
}
""").evaluate(invoiceJson);
grouped.asText(); // {"Bowler Hat":206.7,"Trilby hat":21.67,...}These illustrate common patterns; the full language is documented at docs.jsonata.org.
Field access and navigation
jsonata("Surname").evaluate(person).asText();
jsonata("Address.City").evaluate(person).asText();
jsonata("Other.Nothing").evaluate(person).isEmpty(); // path not foundFiltering and predicates
jsonata("Phone[type='office'].number").evaluate(person).multi().asText();
jsonata("Numbers[>$average(Numbers)]").evaluate(data).multi().asInt();String and numeric expressions
jsonata("FirstName & ' ' & Surname").evaluate(person).asText();
jsonata("$sum(Account.Order.Product.(Price * Quantity))").evaluate(invoice).asDouble();Constructing output
jsonata("""
Account.Order.Product.{
"name": `Product Name`,
"total": Price * Quantity
}
""").evaluate(invoice).asText();Try expressions interactively at try.jsonata.org.
Pass external values into an expression with $variable names. Bindings must be registered before parsing:
import dev.vepo.jsonata.EvaluationEnvironment;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
var env = EvaluationEnvironment.builder()
.bind("threshold", JsonNodeFactory.instance.numberNode(100))
.build();
var expr = jsonata("Numbers[$ > $threshold]", env);
expr.evaluate("{\"Numbers\":[1,50,150,200]}").multi().asInt(); // [150, 200]You can also chain bindings on a compiled expression (returns a new instance):
var expr = jsonata("$greeting & ' ' & Surname")
.bind("greeting", "\"Hello\"");Register Java functions that JSONata expressions can call. Like bindings, functions must exist at parse time:
var env = EvaluationEnvironment.builder()
.registerFunction("add", call -> {
var left = call.arguments().get(0).map(call.original(), call.current());
var right = call.arguments().get(1).map(call.original(), call.current());
var sum = left.toJson().asInt() + right.toJson().asInt();
return dev.vepo.jsonata.functions.data.Data.load(String.valueOf(sum));
})
.build();
jsonata("$add(10, 32)", env).evaluate("{}").asInt(); // 42Each MappingCall provides:
original()— the root input documentcurrent()— the context node where the function was invokedarguments()— unevaluated argument expressions (evaluate with.map(original, current))
| Situation | Exception |
|---|---|
| Invalid JSON input | JSONataException — "Invalid JSON! content=..." |
| Invalid expression syntax | ParseCancellationException (ANTLR) |
| Unknown function at parse time | JSONataException — "Function not found: $name" |
| Runtime evaluation errors | JSONataException or IllegalArgumentException |
Parse expressions inside a try/catch when loading user-supplied expression text; validate JSON input before evaluation in untrusted paths.
Compiled JSONata instances are safe to reuse across threads. Each evaluate call clears internal path bindings before and after execution. For high concurrency, prefer one shared compiled expression over parsing per request.
mvn test # unit tests
mvn verify # tests + JaCoCo coverage
mvn test -Dtest=JsonataConformanceTest#printBaselineReport # conformance pass rateSee ARCHITECTURE.md for internal design and AGENTS.md for contributor workflows.
- JSONata language documentation
- JSONata overview
- Try JSONata online
- jsonata-js reference implementation
Apache License 2.0 — see LICENSE.