This module implements a JSON Type Definition (JTD) validator based on RFC 8927. JTD is a schema language for JSON designed for code generation and portable validation with standardized error indicators. Unlike JSON Schema, JTD uses eight mutually-exclusive forms that make validation simpler and more predictable.
Key Architectural Principles:
- Simpler than JSON Schema: Eight mutually-exclusive forms vs. complex combinatorial logic
- Immutable Design: All schema types are records, validation is pure functions
- Stack-based Validation: Explicit validation stack for error path tracking
- RFC 8927 Compliance: Strict adherence to the specification
- Performance Focused: Minimal allocations, efficient validation paths
JTD defines eight mutually-exclusive schema forms:
- empty - Validates any JSON value (RFC 8927 §2.2.1)
- ref - References a definition in the schema (RFC 8927 §2.2.2)
- type - Validates primitive types (RFC 8927 §2.2.3)
- enum - Validates against a set of string values (RFC 8927 §2.2.4)
- elements - Validates homogeneous arrays (RFC 8927 §2.2.5)
- properties - Validates objects with required/optional fields (RFC 8927 §2.2.6)
- values - Validates objects with homogeneous values (RFC 8927 §2.2.7)
- discriminator - Validates tagged unions (RFC 8927 §2.2.8)
flowchart TD
A[JSON Document] --> B[Json.parse]
B --> C[JsonValue]
C --> D{JTDSchema.compile}
D --> E[Parse Phase]
E --> F[Validation Phase]
F --> G[ValidationResult]
E --> E1[Identify Schema Form]
E --> E2[Extract Definitions]
E --> E3[Build Immutable Records]
F --> F1[Stack-based Validation]
F --> F2[Error Path Tracking]
F --> F3[Standardized Errors]
Following modern Java patterns, we use a package-private sealed interface with record implementations and a public facade class:
package json.java21.jtd;
import jdk.sandbox.java.util.json.*;
/// Package-private sealed interface for schema types
sealed interface JtdSchema
permits JtdSchema.EmptySchema,
JtdSchema.RefSchema,
JtdSchema.TypeSchema,
JtdSchema.EnumSchema,
JtdSchema.ElementsSchema,
JtdSchema.PropertiesSchema,
JtdSchema.ValuesSchema,
JtdSchema.DiscriminatorSchema,
JtdSchema.NullableSchema {
/// Schema type records (package-private)
record EmptySchema() implements JtdSchema {}
record RefSchema(String ref, Map<String, JtdSchema> definitions) implements JtdSchema {}
record TypeSchema(PrimitiveType type) implements JtdSchema {}
record EnumSchema(Set<String> values) implements JtdSchema {}
record ElementsSchema(JtdSchema elements) implements JtdSchema {}
record PropertiesSchema(
Map<String, JtdSchema> properties,
Map<String, JtdSchema> optionalProperties,
boolean additionalProperties
) implements JtdSchema {}
record ValuesSchema(JtdSchema values) implements JtdSchema {}
record DiscriminatorSchema(
String discriminator,
Map<String, JtdSchema> mapping
) implements JtdSchema {}
record NullableSchema(JtdSchema nullable) implements JtdSchema {}
}
/// Public facade class for JTD operations
public class Jtd {
/// Compile and validate JSON against JTD schema
public Result validate(JsonValue schema, JsonValue instance) {
JtdSchema jtdSchema = compileSchema(schema);
return validateWithStack(jtdSchema, instance);
}
/// Validation result
public record Result(boolean isValid, List<String> errors) {}
}JTD supports these primitive types, each with specific validation rules:
enum PrimitiveType {
BOOLEAN,
FLOAT32, FLOAT64,
INT8, UINT8, INT16, UINT16, INT32, UINT32,
STRING,
TIMESTAMP
}Architectural Impact:
- No 64-bit integers (RFC 8927 §2.2.3.1): Simplifies numeric validation
- Timestamp format (RFC 8927 §2.2.3.2): Must be RFC 3339 format
- Float precision (RFC 8927 §2.2.3.3): Separate validation for 32-bit vs 64-bit
sequenceDiagram
participant User
participant JTDSchema
participant ValidationStack
participant ErrorCollector
User->>JTDSchema: validate(json)
JTDSchema->>ValidationStack: push(rootSchema, "#")
loop While stack not empty
ValidationStack->>JTDSchema: pop()
JTDSchema->>JTDSchema: validateCurrent()
alt Validation fails
JTDSchema->>ErrorCollector: addError(path, message)
else Has children
JTDSchema->>ValidationStack: push(children)
end
end
JTDSchema->>User: ValidationResult
JTD specifies standardized error format with:
- instancePath: JSON Pointer to failing value in instance
- schemaPath: JSON Pointer to failing constraint in schema
record ValidationError(
String instancePath, // RFC 8927 §3.2.1
String schemaPath, // RFC 8927 §3.2.2
String message // Human-readable error description
) {}flowchart TD
A[JsonValue Schema] --> B{Identify Form}
B -->|empty| C[EmptySchema]
B -->|ref| D[RefSchema]
B -->|type| E[TypeSchema]
B -->|enum| F[EnumSchema]
B -->|elements| G[ElementsSchema]
B -->|properties| H[PropertiesSchema]
B -->|values| I[ValuesSchema]
B -->|discriminator| J[DiscriminatorSchema]
C --> K[Immutable Record]
D --> K
E --> K
F --> K
G --> K
H --> K
I --> K
J --> K
K --> L[JTDSchema Instance]
JTD allows schema definitions for reuse via $ref:
record CompiledSchema(
JTDSchema root,
Map<String, JTDSchema> definitions // RFC 8927 §2.1
) {}Constraints (RFC 8927 §2.1.1):
- Definitions cannot be nested
- Only top-level definitions allowed
- References must resolve to defined schemas
| Aspect | JTD (This Module) | JSON Schema |
|---|---|---|
| Schema Forms | 8 mutually exclusive | 40+ combinable keywords |
| References | Simple $ref to definitions |
Complex $ref with URI resolution |
| Validation Logic | Exhaustive switch on sealed types | Complex boolean logic with allOf/anyOf/not |
| Error Paths | Simple instance+schema paths | Complex evaluation paths |
| Remote Schemas | Not supported | Full URI resolution |
| Type System | Fixed primitive set | Extensible validation keywords |
- Define sealed interface
JTDSchemawith 8 record implementations - Implement
PrimitiveTypeenum with validation logic - Create
ValidationErrorandValidationResultrecords
- Implement schema form detection (mutually exclusive check)
- Build immutable record hierarchy from JSON
- Handle definitions extraction and validation
- Implement stack-based validation engine
- Add error path tracking (instance + schema paths)
- Implement all 8 schema form validators
- Unit tests for each schema form
- Integration tests with RFC examples
- Error case validation
- Performance benchmarks
import jdk.sandbox.java.util.json.*;
import json.java21.jtd.Jtd;
// Create JTD validator
Jtd jtd = new Jtd();
// Compile JTD schema
String schemaJson = """
{
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"age": { "type": "int32" }
},
"optionalProperties": {
"email": { "type": "string" }
}
}
""";
// Validate JSON
String json = """
{"id": "123", "name": "Alice", "age": 30, "email": "alice@example.com"}
""";
Jtd.Result result = jtd.validate(Json.parse(schemaJson), Json.parse(json));
if (!result.isValid()) {
for (var error : result.errors()) {
System.out.println(error);
}
}Run the official JTD Test Suite:
# Run all JTD spec compliance tests
$(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-jtd -Dtest=JtdSpecIT- Immutable Records: Zero mutation during validation
- Stack-based Validation: Explicit stack vs recursion prevents StackOverflowError
- Minimal Allocations: Reuse validation context objects
- Early Exit: Fail fast on first validation error (when appropriate)
- Type-specific Validation: Optimized paths for each primitive type
- Schema Compilation:
IllegalArgumentExceptionfor invalid schemas - Validation: Never throws, returns
ValidationResultwith errors - Definitions: Validate all definitions exist at compile time
- Type Checking: Strict RFC 8927 compliance for all primitive types
- Form:
empty = {} - Behavior: accepts all instances; produces no validation errors.
- RFC 8927 §3.3.1: "If a schema is of the 'empty' form, then it accepts all instances. A schema of the 'empty' form will never produce any error indicators."
- Common pitfall: confusing JTD with non-JTD validators that treat
{}as an empty-object schema. - Implementation: compile
{}toEmptySchemaand validate everything as OK.
This implementation strictly follows RFC 8927:
- ✅ Eight mutually-exclusive schema forms
- ✅ Standardized error format (instancePath, schemaPath)
- ✅ Primitive type validation (no 64-bit integers)
- ✅ Definition support (non-nested)
- ✅ Timestamp format validation (RFC 3339)
- ✅ No remote schema support (simplification by design)
Potential future additions (non-RFC compliant):
- Custom type validators
- Additional format validators
- Remote definition support
- Performance optimizations for specific use cases