A Java implementation of the JSON Type Definition (JTD) specification (RFC 8927). JTD is a schema language for JSON that provides simple, predictable validation with eight mutually-exclusive schema forms.
This interpreter-based validator is optimised for infrequent validation — loading configuration files, validating resources at startup, or one-off schema checks. Because these operations run rarely, the validated classes are unlikely to be JIT-compiled, and the interpreter’s simplicity keeps startup overhead low. After validation completes, the schema and document objects are typically discarded, so the JVM is not encumbered with loaded validator classes and can focus JIT effort on the application’s hot path.
For repeated hot-path validation (e.g., event processing, API gateways), consider the bytecode codegen module which generates dedicated validator classes that benefit from JIT optimisation over many invocations.
Future note:
java.util.jsonhas entered the JDK incubator (jdk.incubator.json). Once the API stabilises in the JDK itself, generated bytecode validators can depend directly on future JDK classes rather than this backport, making them even more efficient with zero library overhead.
import json.java21.jtd.Jtd;
import jdk.incubator.java.util.json.*;
// Create a JTD schema
String schemaJson = """
{
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"age": { "type": "int32" }
},
"optionalProperties": {
"email": { "type": "string" }
}
}
""";
// Parse and validate
JsonValue schema = Json.parse(schemaJson);
JsonValue data = Json.parse("{\"id\": \"123\", \"name\": \"Alice\", \"age\": 30}");
Jtd validator = new Jtd();
Jtd.Result result = validator.validate(schema, data);
if (result.isValid()) {
System.out.println("Valid!");
} else {
result.errors().forEach(System.out::println);
}
JTD defines eight mutually-exclusive schema forms:
Accepts any JSON value:
{}
References a definition:
{"ref": "address"}
Validates primitive types:
{"type": "string"}
Supported types: boolean, string, timestamp, int8, uint8, int16, uint16, int32, uint32, float32, float64
Integer types (int8, uint8, int16, uint16, int32, uint32) validate based on numeric value, not textual representation:
3, 3.0, 3.000, 42.00 (mathematically integers)3.1, 3.14, 3.0001 (have fractional components)This follows RFC 8927 §2.2.3.1: “An integer value is a number without a fractional component.”
Validates against string values:
{"enum": ["red", "green", "blue"]}
Validates homogeneous arrays:
{"elements": {"type": "string"}}
Validates objects with required/optional fields:
{
"properties": {
"id": {"type": "string"},
"name": {"type": "string"}
},
"optionalProperties": {
"email": {"type": "string"}
}
}
Validates objects with homogeneous values:
{"values": {"type": "string"}}
Validates tagged unions:
{
"discriminator": "type",
"mapping": {
"person": {"properties": {"name": {"type": "string"}}},
"company": {"properties": {"name": {"type": "string"}}}
}
}
Discriminator Constraints (RFC 8927 §2.2.8):
properties schemas (not primitive types)nullable: trueThese constraints are enforced at compile-time for predictable validation behavior.
Any schema can be made nullable by adding "nullable": true:
{"type": "string", "nullable": true}
Schemas can define reusable components:
{
"definitions": {
"address": {
"properties": {
"street": {"type": "string"},
"city": {"type": "string"}
}
}
},
"properties": {
"home": {"ref": "address"},
"work": {"ref": "address"}
}
}
Validation errors include standardized information:
[off=45 ptr=/age via=#→field:age] expected int32, got string
# Build the module
./mvnw compile -pl json-java21-jtd -am
# Run tests
./mvnw test -pl json-java21-jtd -am
# Run RFC compliance tests
./mvnw test -pl json-java21-jtd -am -Dtest=JtdSpecIT
# Run with detailed logging
./mvnw test -pl json-java21-jtd -am -Djava.util.logging.ConsoleHandler.level=FINE
A schema can be compiled into a reusable JtdValidator – a functional interface
(JsonValue -> JtdValidationResult) suitable for stream pipelines:
import json.java21.jtd.JtdValidator;
import json.java21.jtd.JtdValidationResult;
import jdk.incubator.java.util.json.*;
String schemaJson = """
{ "type": "string" }
""";
JsonValue schema = Json.parse(schemaJson);
// Compile to a reusable validator (interpreter path, always available)
JtdValidator validator = JtdValidator.compileInterpreter(schema);
JtdValidationResult result = validator.validate(Json.parse("\"hello\""));
assert result.isValid();
// Use in a stream pipeline
List<JsonValue> docs = ...;
List<JsonValue> invalid = docs.stream()
.filter(doc -> !validator.validate(doc).isValid())
.toList();
Errors follow RFC 8927 exactly – each error is an (instancePath, schemaPath) pair:
JtdValidationResult result = validator.validate(Json.parse("42"));
result.errors().forEach(e ->
System.out.println(e.instancePath() + " -> " + e.schemaPath()));
// Output: "" -> "/type"
For repeated hot-path validation, the json-java21-jtd-codegen module generates dedicated validator classes via the JDK 24+ ClassFile API. Use its own JtdValidator.compileGenerated(schema) factory — a separate functional interface in the json.java21.jtd.codegen package.
The validator uses a stack-based approach for efficient validation:
See ARCHITECTURE.md for detailed implementation information. See JTD_STACK_MACHINE_SPEC.md for the interpreter specification. See JTD_CODEGEN_SPEC.md for the code generation specification.
This implementation is fully compliant with RFC 8927:
This project is part of the OpenJDK JSON API implementation and follows the same licensing terms.