Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: jdt2jar/Dockerfile
file: jtd2jar/Dockerfile
push: false
load: true
tags: jdt2jar:ci
tags: jtd2jar:ci

- name: Smoke test
run: |
docker run --rm jdt2jar:ci --help
docker run --rm jtd2jar:ci --help
6 changes: 3 additions & 3 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: jdt2jar/Dockerfile
file: jtd2jar/Dockerfile
push: false
load: true
tags: jdt2jar:ci
tags: jtd2jar:ci

- name: Smoke test
run: |
docker run --rm jdt2jar:ci --help
docker run --rm jtd2jar:ci --help
12 changes: 6 additions & 6 deletions .github/workflows/release-on-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ jobs:
Pre-built distroless container image available on GitHub Container Registry:

```bash
docker pull ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jdt2jar:${{ steps.version.outputs.version }}
docker pull ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jdt2jar:latest
docker pull ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jtd2jar:${{ steps.version.outputs.version }}
docker pull ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jtd2jar:latest
```

See [jdt2jar/README.md](https://github.com/${{ github.repository }}/blob/main/jdt2jar/README.md) for usage.
See [jtd2jar/README.md](https://github.com/${{ github.repository }}/blob/main/jtd2jar/README.md) for usage.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Link release notes to the tagged documentation

During the first renamed release, the Create GitHub Release with notes step runs before this workflow opens the back-merge PR, so main/jtd2jar/README.md does not yet exist and this usage link is broken until that PR is manually merged. Link to the release tag (for example ${{ github.ref_name }}) rather than main so the documentation is available when the release is published.

AGENTS.md reference: AGENTS.md:L31-L35

Useful? React with 👍 / 👎.


- name: Build and Deploy to Central (release profile)
id: deploy
Expand Down Expand Up @@ -150,8 +150,8 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: jdt2jar/Dockerfile
file: jtd2jar/Dockerfile
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jdt2jar:${{ steps.version.outputs.version }}
ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jdt2jar:latest
ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jtd2jar:${{ steps.version.outputs.version }}
ghcr.io/${{ github.repository_owner }}/java.util.json.java21/jtd2jar:latest
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This repo is organized into the following modules:
| `json-java21` | Core `java.util.json` backport (parser, immutable types, `Json` API) | 21+ |
| `json-java21-jtd` | JTD (RFC 8927) stack-machine interpreter — ideal for infrequent config parsing and one-time validation | 21+ |
| `json-java21-jtd-codegen` | Bytecode code generator for JTD schemas — ahead-of-time compiled validators for repeated hot-path validation | 24+ (auto-skipped on JDK 21) |
| `jdt2jar` | CLI + distroless container to pre-compile JTD schemas into standalone validator JARs (eliminates JDK 24+ runtime requirement) | 24+ (auto-skipped on JDK 21) |
| `jtd2jar` | CLI + distroless container to pre-compile JTD schemas into standalone validator JARs (eliminates JDK 24+ runtime requirement) | 24+ (auto-skipped on JDK 21) |
| `json-java21-jsonpath` | JsonPath query engine over `jdk.incubator.java.util.json` values (Goessner-style: filters, slices, recursive descent, unions) | 21+ |
| `json-compatibility-suite` | JSON Test Suite conformance reporter (tests against [nst/JSONTestSuite](https://github.com/nst/JSONTestSuite)) | 21+ |
| `json-java21-api-tracker` | Daily upstream API drift detector — fetches OpenJDK sandbox sources, compares public API signatures, reports differences | 25+ |
Expand Down Expand Up @@ -393,9 +393,9 @@ Jtd.Result result = validator.validate(schema, data);

## JTD to JAR Compiler (Optional)

An optional `jdt2jar` CLI tool and distroless Docker image are available for pre-compiling JTD schemas into standalone validator JARs at build time. This eliminates the JDK 24+ runtime requirement for generated validators — the JARs run on JDK 21+.
An optional `jtd2jar` CLI tool and distroless Docker image are available for pre-compiling JTD schemas into standalone validator JARs at build time. This eliminates the JDK 24+ runtime requirement for generated validators — the JARs run on JDK 21+.

See [`jdt2jar/README.md`](jdt2jar/README.md) for build instructions, container usage, and the pre-built image on GitHub Container Registry (`ghcr.io`).
See [`jtd2jar/README.md`](jtd2jar/README.md) for build instructions, container usage, and the pre-built image on GitHub Container Registry (`ghcr.io`).

## Building

Expand Down
2 changes: 1 addition & 1 deletion jdt2jar/.dockerignore → jtd2jar/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
.env

# Local test artifacts
/tmp/jdt2jar-*/
/tmp/jtd2jar-*/
8 changes: 4 additions & 4 deletions jdt2jar/Dockerfile → jtd2jar/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
FROM eclipse-temurin:25-jdk AS build
WORKDIR /build
COPY . .
RUN ["./mvnw", "-pl", "jdt2jar", "-am", "package", "-DskipTests", "-Dsurefire.failIfNoSpecifiedTests=false"]
RUN ["java", "-cp", "/build/jdt2jar/target/jdt2jar.jar", "json.java21.jdt2jar.build.DockerImageBuilder", "/build/jdt2jar/target/jdt2jar.jar", "/opt/jre"]
RUN ["./mvnw", "-pl", "jtd2jar", "-am", "package", "-DskipTests", "-Dsurefire.failIfNoSpecifiedTests=false"]
RUN ["java", "-cp", "/build/jtd2jar/target/jtd2jar.jar", "json.java21.jtd2jar.build.DockerImageBuilder", "/build/jtd2jar/target/jtd2jar.jar", "/opt/jre"]
RUN ["mkdir", "-p", "/empty-work/tmp", "/empty-app"]

FROM gcr.io/distroless/base-debian13:nonroot
Expand All @@ -13,5 +13,5 @@ COPY --from=build --chown=65532:65532 /empty-app /app
WORKDIR /work
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.io.tmpdir=/work/tmp -XX:+ExitOnOutOfMemoryError"
COPY --from=build /opt/jre /jre
COPY --from=build /build/jdt2jar/target/jdt2jar.jar /app/jdt2jar.jar
ENTRYPOINT ["/jre/bin/java","-jar","/app/jdt2jar.jar"]
COPY --from=build /build/jtd2jar/target/jtd2jar.jar /app/jtd2jar.jar
ENTRYPOINT ["/jre/bin/java","-jar","/app/jtd2jar.jar"]
28 changes: 15 additions & 13 deletions jdt2jar/README.md → jtd2jar/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# jdt2jar
# jtd2jar

`jdt2jar` compiles a JTD schema into a standalone validator JAR at build time. The generated JAR runs on JDK 21+ with no JDK 25+ runtime dependency.
`jtd2jar` compiles a JTD schema into a standalone validator JAR at build time. The generated JAR runs on JDK 21+ with no JDK 25+ runtime dependency.

## Use Case

This tool bridges the gap between the interpreter and codegen paths:

- **Interpreter** ([`json-java21-jtd`](../json-java21-jtd/README.md)): ideal for infrequent config parsing — simple, no build step, runs on JDK 21+.
- **Codegen** ([`json-java21-jtd-codegen`](../json-java21-jtd-codegen/README.md)): ideal for repeated hot-path validation — ~9x faster, but requires JDK 25+ at runtime.
- **jdt2jar**: pre-compiles schemas into validator JARs at build time (using JDK 25+), then deploys them to any JDK 21+ runtime. Best for CI/CD pipelines, distroless containers, or environments where you want JIT-optimised validators without shipping a JDK 25+ runtime.
- **jtd2jar**: pre-compiles schemas into validator JARs at build time (using JDK 25+), then deploys them to any JDK 21+ runtime. Best for CI/CD pipelines, distroless containers, or environments where you want JIT-optimised validators without shipping a JDK 25+ runtime.

> **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.

## CLI

```bash
jdt2jar <schema.json> [options]
jtd2jar <schema.json> [options]
```

Options:
Expand All @@ -35,24 +35,26 @@ A minimal distroless container image is available for offline schema compilation
### Pre-built Image (GitHub Container Registry)

```bash
docker pull ghcr.io/simbo1905/java.util.json.java21/jdt2jar:latest
docker pull ghcr.io/simbo1905/java.util.json.java21/jdt2jar:2026.05.20
docker pull ghcr.io/simbo1905/java.util.json.java21/jtd2jar:latest
docker pull ghcr.io/simbo1905/java.util.json.java21/jtd2jar:2026.05.20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the historical tag on the old image path

The documented 2026.05.20 pull uses the new jtd2jar image path, but the note immediately below states that every release through 2026.08.30 was published only under jdt2jar. A user copying this pinned-version example will therefore request a tag that was never published; retain the old repository path for this example or show a tag actually published under the new name.

AGENTS.md reference: AGENTS.md:L31-L35

Useful? React with 👍 / 👎.

```

> **Name change**: releases up to and including `2026.08.30` published this image under the misspelled name `ghcr.io/simbo1905/java.util.json.java21/jdt2jar`. Those tags remain on GHCR but are frozen; re-point to `jtd2jar`.

### Build Locally

Requires Docker and JDK 25+ (for the build stage). Build from the repository root:

```bash
docker build -t jdt2jar -f jdt2jar/Dockerfile .
docker build -t jtd2jar -f jtd2jar/Dockerfile .
```

### Usage

The container's working directory is `/work`. Mount your project directory there:

```bash
docker run --rm -v "$(pwd):/work" jdt2jar:latest schema.jtd.json --output schema-validator.jar --main
docker run --rm -v "$(pwd):/work" jtd2jar:latest schema.jtd.json --output schema-validator.jar --main
```

Validate a payload with the generated JAR:
Expand All @@ -64,18 +66,18 @@ java -jar schema-validator.jar --validate payload.json
Or validate inside a container:

```bash
docker run --rm -v "$(pwd):/work" --entrypoint /jre/bin/java jdt2jar:latest -jar /work/schema-validator.jar --validate /work/payload.json
docker run --rm -v "$(pwd):/work" --entrypoint /jre/bin/java jtd2jar:latest -jar /work/schema-validator.jar --validate /work/payload.json
```

### Helper Script

For environments where volume mounts are restricted (e.g., Colima on macOS with projects outside `~/`), use the helper script:

```bash
./scripts/jdt2jar.sh schema.jtd.json --output schema-validator.jar --main
./scripts/jtd2jar.sh schema.jtd.json --output schema-validator.jar --main
```

The script syncs source to `~/tmp/jdt2jar-work`, runs the container, and syncs output back.
The script syncs source to `~/tmp/jtd2jar-work`, runs the container, and syncs output back.

### Image Properties

Expand All @@ -89,6 +91,6 @@ The script syncs source to `~/tmp/jdt2jar-work`, runs the container, and syncs o
### Security Scanning

```bash
syft packages image:ghcr.io/simbo1905/java.util.json.java21/jdt2jar:latest
grype ghcr.io/simbo1905/java.util.json.java21/jdt2jar:latest
syft packages image:ghcr.io/simbo1905/java.util.json.java21/jtd2jar:latest
grype ghcr.io/simbo1905/java.util.json.java21/jtd2jar:latest
```
10 changes: 5 additions & 5 deletions jdt2jar/pom.xml → jtd2jar/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
<version>2026.08.30</version>
</parent>

<artifactId>jdt2jar</artifactId>
<artifactId>jtd2jar</artifactId>
<packaging>jar</packaging>
<name>jdt2jar CLI</name>
<name>jtd2jar CLI</name>
<description>Offline JTD-to-JAR compiler that packages generated validators into standalone JAR files.</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>24</maven.compiler.release>
<project.build.finalName>jdt2jar</project.build.finalName>
<project.build.finalName>jtd2jar</project.build.finalName>
</properties>

<dependencies>
Expand Down Expand Up @@ -86,10 +86,10 @@
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
<finalName>jdt2jar</finalName>
<finalName>jtd2jar</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>json.java21.jdt2jar.Jdt2Jar</mainClass>
<mainClass>json.java21.jtd2jar.Jtd2Jar</mainClass>
</transformer>
</transformers>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package json.java21.jdt2jar;
package json.java21.jtd2jar;

import jdk.incubator.java.util.json.Json;
import json.java21.jtd.codegen.JtdCodegen;
Expand All @@ -23,26 +23,26 @@
///
/// Builds a standalone validator JAR from a schema file and can optionally
/// include a `java -jar` entry point and a companion source file.
public final class Jdt2Jar {
public final class Jtd2Jar {

static final Logger LOG = Logger.getLogger(Jdt2Jar.class.getName());
static final Logger LOG = Logger.getLogger(Jtd2Jar.class.getName());

private static final int DEFAULT_RUNTIME = 21;
private static final String DEFAULT_PACKAGE = "jtd.generated";
private static final String DEFAULT_CLASS = "SchemaValidator";
private static final String PROPERTIES_ENTRY = "jdt2jar.properties";
private static final String PROPERTIES_ENTRY = "jtd2jar.properties";
private static final String SCHEMA_ENTRY = "jtd/schema.json";
private static final String MAIN_CLASS = "json.java21.jdt2jar.runtime.ValidatorMain";
private static final String MAIN_CLASS = "json.java21.jtd2jar.runtime.ValidatorMain";

private Jdt2Jar() {}
private Jtd2Jar() {}

public static void main(String[] args) {
System.exit(run(args));
}

public static int run(String[] args) {
try {
return new Jdt2Jar().execute(args);
return new Jtd2Jar().execute(args);
} catch (UsageException e) {
System.err.println(e.getMessage());
System.err.println();
Expand Down Expand Up @@ -86,7 +86,7 @@ private static void writeValidatorJar(Path output, Options options, String schem
final var manifest = new Manifest();
final var attrs = manifest.getMainAttributes();
attrs.putValue("Manifest-Version", "1.0");
attrs.putValue("Created-By", "jdt2jar");
attrs.putValue("Created-By", "jtd2jar");
if (options.main()) {
attrs.putValue("Main-Class", MAIN_CLASS);
}
Expand Down Expand Up @@ -163,7 +163,7 @@ private static boolean shouldCopyRuntime(String path) {
|| path.startsWith("json/java21/jtd/"))
&& !path.startsWith("json/java21/jtd/codegen/")
|| path.startsWith("json/java21/jtd/codegen/JtdValidator.class")
|| path.startsWith("json/java21/jdt2jar/runtime/");
|| path.startsWith("json/java21/jtd2jar/runtime/");
}

private static void writeEntry(JarOutputStream out, Set<String> written, String name, byte[] bytes)
Expand All @@ -184,7 +184,7 @@ private static byte[] propertiesBytes(Options options) {
props.setProperty("schemaEntry", SCHEMA_ENTRY);
props.setProperty("runtime", Integer.toString(options.runtime()));
try (final var out = new ByteArrayOutputStream()) {
props.store(out, "jdt2jar");
props.store(out, "jtd2jar");
return out.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand All @@ -204,7 +204,7 @@ private static void writeSourceFile(Path sourcePath, Options options) throws IOE

import jdk.incubator.java.util.json.JsonValue;
import json.java21.jtd.JtdValidationResult;
import json.java21.jdt2jar.runtime.ValidatorMain;
import json.java21.jtd2jar.runtime.ValidatorMain;

public final class %s implements json.java21.jtd.JtdValidator {
private final String schemaJson;
Expand Down Expand Up @@ -308,7 +308,7 @@ private static String toQualifiedName(String packageName, String className) {

private static void printUsage() {
System.out.println("""
jdt2jar <schema.json> [options]
jtd2jar <schema.json> [options]

Options:
--output <path> Output JAR path (default: <schema-name>-validator.jar)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package json.java21.jdt2jar.build;
package json.java21.jtd2jar.build;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand All @@ -16,7 +16,7 @@ private DockerImageBuilder() {}

public static void main(String[] args) throws Exception {
if (args.length != 2) {
throw new IllegalArgumentException("Usage: DockerImageBuilder <jdt2jar.jar> <jre-output-dir>");
throw new IllegalArgumentException("Usage: DockerImageBuilder <jtd2jar.jar> <jre-output-dir>");
}
final var jar = Path.of(args[0]);
final var jreOutput = Path.of(args[1]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package json.java21.jdt2jar.runtime;
package json.java21.jtd2jar.runtime;

import jdk.incubator.java.util.json.Json;
import jdk.incubator.java.util.json.JsonObject;
Expand All @@ -19,7 +19,7 @@
/// The CLI jar copies this class into the generated output when `--main` is
/// requested and points the manifest `Main-Class` at it.
public final class ValidatorMain {
private static final String CONFIG_RESOURCE = "jdt2jar.properties";
private static final String CONFIG_RESOURCE = "jtd2jar.properties";

private ValidatorMain() {}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package json.java21.jdt2jar;
package json.java21.jtd2jar;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
Expand All @@ -11,7 +11,7 @@

import static org.assertj.core.api.Assertions.assertThat;

class Jdt2JarCliTest extends Jdt2JarTestBase {
class Jtd2JarCliTest extends Jtd2JarTestBase {

@TempDir
Path tempDir;
Expand All @@ -28,16 +28,16 @@ void compilesStandaloneValidatorJar() throws Exception {
{"name":"Alice"}
""", StandardCharsets.UTF_8);

assertThat(Jdt2Jar.run(new String[] {schema.toString(), "--output", output.toString(), "--main"})).isZero();
assertThat(Jtd2Jar.run(new String[] {schema.toString(), "--output", output.toString(), "--main"})).isZero();

assertThat(output).exists();
try (final var jar = new JarFile(output.toFile())) {
assertThat(jar.getEntry("jtd/generated/SchemaValidator.class")).isNotNull();
assertThat(jar.getEntry("jtd/schema.json")).isNotNull();
assertThat(jar.getEntry("jdt2jar.properties")).isNotNull();
assertThat(jar.getEntry("json/java21/jdt2jar/runtime/ValidatorMain.class")).isNotNull();
assertThat(jar.getEntry("jtd2jar.properties")).isNotNull();
assertThat(jar.getEntry("json/java21/jtd2jar/runtime/ValidatorMain.class")).isNotNull();
assertThat(jar.getManifest().getMainAttributes().getValue("Main-Class"))
.isEqualTo("json.java21.jdt2jar.runtime.ValidatorMain");
.isEqualTo("json.java21.jtd2jar.runtime.ValidatorMain");
}

final var validResult = runJavaJar(output, "--validate", payload.toString());
Expand All @@ -61,7 +61,7 @@ void writesCompanionSourceWhenRequested() throws Exception {
""", StandardCharsets.UTF_8);
final var output = tempDir.resolve("widget-validator.jar");

assertThat(Jdt2Jar.run(new String[] {
assertThat(Jtd2Jar.run(new String[] {
schema.toString(),
"--output", output.toString(),
"--package", "demo.validator",
Expand Down
Loading
Loading