Skip to content

Latest commit

 

History

History
80 lines (57 loc) · 3.89 KB

File metadata and controls

80 lines (57 loc) · 3.89 KB

JTD Bytecode Codegen

JTD schema-to-bytecode compiler using the JDK 24+ ClassFile API (JEP 484). Generates Java 21-compatible .class files at runtime for ~9x faster validation on hot paths.

Use Case

This codegen module is optimised for repeated hot-path validation — event processing pipelines, API gateways, message brokers, or any scenario where the same schema validates thousands or millions of documents. The generated validator classes contain only the checks the schema requires (no interpreter, no AST, no runtime stack), and after sufficient invocations the JIT compiler inlines and optimises them to near-native speed.

For infrequent validation (config loading, startup checks, one-off validation), the interpreter-based validator is simpler and avoids the codegen overhead entirely.

Future note: java.util.json has 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.

Requirements

  • Build: JDK 24+ (uses ClassFile API, JEP 484)
  • Generated bytecode: Java 21+ (classfile version 65)
  • Runtime: JDK 24+ (the codegen itself runs at runtime to generate classes)

How It Works

  1. JtdCodegen.compile(schema) parses a JTD schema into an AST
  2. The emitter walks the AST and generates bytecode using ClassFile.of().build()
  3. The generated class is loaded via MethodHandles.Lookup.defineClass()
  4. The resulting JtdValidator validates JSON in ~9x less time than the interpreter

Usage

import jdk.sandbox.java.util.json.*;
import json.java21.jtd.codegen.JtdValidator;

JsonValue schema = Json.parse("""
    {"properties": {"name": {"type": "string"}, "age": {"type": "uint8"}}}
    """);
JsonValue doc = Json.parse("{\"name\":\"Alice\",\"age\":30}");

// Codegen path (JDK 24+ only)
JtdValidator validator = JtdValidator.compileGenerated(schema);
var result = validator.validate(doc);
System.out.println(result.isValid()); // true

// Falls back to interpreter automatically via JtdValidator.compileInterpreter()
JtdValidator interp = json.java21.jtd.JtdValidator.compileInterpreter(schema);

Performance

Benchmark results (50k warmup, 200k measured iterations):

Schema Valid doc Invalid doc Classfile size
simple-type 6.8x faster 2.2x 990 B
enum-5 1.2x 1.5x 1,162 B
nullable-int 6.3x 2.2x 1,205 B
properties-3 9.5x 5.3x 2,453 B
props-with-optional 12.3x 2.3x 2,112 B
elements-of-type 47.3x 3.5x 1,486 B
values-of-type 17.4x 4.0x 1,816 B
nested-elements-of-props 11.2x 4.1x 2,698 B
discriminator-2-variants 3.3x 3.5x 2,671 B
ref-with-definitions 1.9x 0.7x 3,008 B
deep-nesting 0.7x 0.5x 3,406 B
worked-example-rfc8927 2.9x 3.6x 5,565 B
Average 8.7x 3.1x

Codegen shines for array iteration (elements-of-type at 47x, values-of-type at 17x) and property validation (9-12x). It's slower on deeply nested schemas and ref-heavy schemas where bytecode overhead outweighs interpreter dispatch cost.

When to Use Codegen

  • Use codegen when: validating the same schema against many documents (e.g., event processing, API gateways)
  • Use interpreter when: one-off validation, JDK 21 runtime, or schemas dominated by deep refs/nesting

Build

# Automatically included on JDK 24+, skipped on JDK 21
./mvnw test -pl json-java21-jtd-codegen -am -Djava.util.logging.ConsoleHandler.level=INFO

The parent pom.xml uses a profile (jtd-codegen) that activates on <jdk>[24,)</jdk>, so the module is automatically skipped when building with JDK 21.