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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 24
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
distribution: oracle
java-version: '24'
java-version: '25'
cache: 'maven'

- name: Build and verify
Expand Down
3 changes: 2 additions & 1 deletion jdt2jar/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1

FROM eclipse-temurin:24-jdk AS build
FROM eclipse-temurin:25-jdk AS build
WORKDIR /build
COPY . .
RUN ["./mvnw", "-pl", "jdt2jar", "-am", "package", "-DskipTests", "-Dsurefire.failIfNoSpecifiedTests=false"]
Expand All @@ -10,6 +10,7 @@ RUN ["mkdir", "-p", "/empty-work/tmp", "/empty-app"]
FROM gcr.io/distroless/base-debian13:nonroot
COPY --from=build --chown=65532:65532 /empty-work /work
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
Expand Down
58 changes: 31 additions & 27 deletions jdt2jar/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# jdt2jar

`jdt2jar` compiles a JTD schema into a standalone validator JAR at build time. The generated JAR runs on JDK 21+ with no JDK 24+ runtime dependency.
`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.

## 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 24+ at runtime.
- **jdt2jar**: pre-compiles schemas into validator JARs at build time (using JDK 24+), 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 24+ runtime.
- **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.

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 minimum JDK requirement aligned with the build

This now tells users that codegen requires JDK 25+, but json-java21-jtd-codegen/pom.xml and jdt2jar/pom.xml still compile with release 24, the parent profile activates on [24,), and both the top-level and codegen READMEs continue to document 24+. Either retain the 24+ minimum here or update the actual build requirement and all user documentation together so JDK 24 users are not incorrectly told the supported path is unavailable.

AGENTS.md reference: AGENTS.md:L29-L29

Useful? React with 👍 / 👎.

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

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

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

```bash
# Pull the latest image
docker pull ghcr.io/simbo1905/java.util.json.java21/jdt2jar:latest

# Pull a specific release version
docker pull ghcr.io/simbo1905/java.util.json.java21/jdt2jar:2026.02.05
docker pull ghcr.io/simbo1905/java.util.json.java21/jdt2jar:2026.05.20
```

### Build Locally

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

```bash
docker build -t jdt2jar -f jdt2jar/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
```

Validate a payload with the generated JAR:

```bash
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
```

### Helper Script

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

```bash
# Show help
docker run --rm ghcr.io/simbo1905/java.util.json.java21/jdt2jar:latest --help

# Compile a schema to a validator JAR (using docker cp for file I/O)
cid=$(docker create --name jdt2jar-build ghcr.io/simbo1905/java.util.json.java21/jdt2jar:latest /work/person.jtd.json --output /work/person-validator.jar --main)
docker cp person.jtd.json jdt2jar-build:/work/person.jtd.json
docker start -a jdt2jar-build
docker cp jdt2jar-build:/work/person-validator.jar .
docker rm jdt2jar-build

# Validate a payload with the generated JAR
java -jar person-validator.jar --validate payload.json
# Or validate inside a container
cid=$(docker create --name jdt2jar-validate --entrypoint /jre/bin/java ghcr.io/simbo1905/java.util.json.java21/jdt2jar:latest -jar /work/person-validator.jar --validate /work/payload.json)
docker cp person-validator.jar jdt2jar-validate:/work/person-validator.jar
docker cp payload.json jdt2jar-validate:/work/payload.json
docker start -a jdt2jar-validate
docker rm jdt2jar-validate
./scripts/jdt2jar.sh schema.jtd.json --output schema-validator.jar --main
```

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

### Image Properties

- **Base**: `gcr.io/distroless/base-debian13:nonroot`
- **Runtime**: jlink-minimized JDK 24 (~40 MB)
- **Runtime**: jlink-minimized JDK 25 LTS (~40 MB)
- **Total size**: ~111 MB disk / ~31 MB content
- **User**: `nonroot` (uid 65532)
- **Shell**: none (distroless)
Expand Down
25 changes: 25 additions & 0 deletions scripts/jdt2jar.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# jdt2jar helper script for environments where volume mounts are restricted
# (e.g., Colima on macOS with projects outside ~/).
#
# Usage:
# ./scripts/jdt2jar.sh <schema.jtd.json> [options...]
#
# Example:
# ./scripts/jdt2jar.sh .tmp/test.jtd.json --output .tmp/test.jar --main

set -euo pipefail

WORK_DIR="$HOME/tmp/jdt2jar-work"
IMAGE="${JDT2JAR_IMAGE:-jdt2jar:latest}"

mkdir -p "$WORK_DIR"

# Sync source (respecting .gitignore)
rsync -a --filter=':- .gitignore' --exclude='.git/' . "$WORK_DIR/"

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 Stage ignored schema inputs before invoking Docker

When the requested schema is under any ignored path, this filter omits it from the staging directory while line 22 passes the original path unchanged to the container, so compilation fails with Schema file not found. This affects the script's own .tmp/test.jtd.json example because the repository .gitignore excludes .tmp; as rsync --help describes, --filter adds file-filtering rules, so the explicitly requested schema must be copied regardless of ignore rules.

Useful? React with 👍 / 👎.


# Run jdt2jar in container
docker run --rm -v "$WORK_DIR:/work" "$IMAGE" "$@"

# Sync output back (any new .jar or .java files in the work dir)
rsync -a --include='*.jar' --include='*.java' --exclude='*' "$WORK_DIR/" ./

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 Recurse into output directories when syncing artifacts

When --output names a nested path such as the script's own .tmp/test.jar example, this filter does not copy the generated JAR or companion source back. rsync --help describes --include as preventing matching files from being excluded, but the parent .tmp/ directory still matches the final --exclude='*', so rsync never descends to the included files; directory traversal must also be included.

Useful? React with 👍 / 👎.

Loading