From c8ff3acf090ac6cf19bfbb08092f27c5ad36d09f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 08:48:04 +0000 Subject: [PATCH] Add jdk28-preview module: JDK 28 early-access build, value classes, strict init flag, PEM, JSON incubator Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Gth99spB1CzQmBFofXmU4Z --- jdk28-preview/README.md | 21 +++++++ jdk28-preview/output/00-environment.txt | 7 +++ jdk28-preview/output/01-value-classes.txt | 8 +++ .../output/02-forgot-the-preview-flag.txt | 9 +++ jdk28-preview/output/03-strict-init-flags.txt | 7 +++ jdk28-preview/output/04-pem-encodings.txt | 11 ++++ jdk28-preview/output/05-json-incubator.txt | 24 +++++++ jdk28-preview/output/06-shenandoah.txt | 8 +++ jdk28-preview/output/07-jep-status.txt | 7 +++ jdk28-preview/scripts/run-all.sh | 62 +++++++++++++++++++ jdk28-preview/src/IntegerIdentity.java | 7 +++ jdk28-preview/src/JsonDemo.java | 13 ++++ jdk28-preview/src/PemRoundTrip.java | 15 +++++ jdk28-preview/src/StrictFieldProbe.java | 6 ++ jdk28-preview/src/ValueClasses.java | 16 +++++ 15 files changed, 221 insertions(+) create mode 100644 jdk28-preview/README.md create mode 100644 jdk28-preview/output/00-environment.txt create mode 100644 jdk28-preview/output/01-value-classes.txt create mode 100644 jdk28-preview/output/02-forgot-the-preview-flag.txt create mode 100644 jdk28-preview/output/03-strict-init-flags.txt create mode 100644 jdk28-preview/output/04-pem-encodings.txt create mode 100644 jdk28-preview/output/05-json-incubator.txt create mode 100644 jdk28-preview/output/06-shenandoah.txt create mode 100644 jdk28-preview/output/07-jep-status.txt create mode 100755 jdk28-preview/scripts/run-all.sh create mode 100644 jdk28-preview/src/IntegerIdentity.java create mode 100644 jdk28-preview/src/JsonDemo.java create mode 100644 jdk28-preview/src/PemRoundTrip.java create mode 100644 jdk28-preview/src/StrictFieldProbe.java create mode 100644 jdk28-preview/src/ValueClasses.java diff --git a/jdk28-preview/README.md b/jdk28-preview/README.md new file mode 100644 index 0000000..f29c36e --- /dev/null +++ b/jdk28-preview/README.md @@ -0,0 +1,21 @@ +# jdk28-preview + +Companion module for the ankurm.com post *What's Coming in JDK 28: Value Classes Preview and Strict Field Initialization*. + +Runs small programs on an **early-access** OpenJDK 28 build (`28-ea+17`, downloaded from jdk.java.net/28) and on JDK 25.0.4.1 for comparison. Early-access builds change weekly, and JEP status changes with them, so treat every transcript as a snapshot and rerun `scripts/run-all.sh` against a newer build. + +```bash +export JDK28=/path/to/jdk-28-ea JDK25=/path/to/jdk-25 +./scripts/run-all.sh +``` + +| Output | Shows | +|---|---| +| `00-environment.txt` | The exact builds used | +| `01-value-classes.txt` | JEP 401: `==` on value records, `Integer` identity, synchronizing on a value object | +| `02-forgot-the-preview-flag.txt` | The two errors you get without `--enable-preview` | +| `03-strict-init-flags.txt` | JEP 539: the `ACC_STRICT_INIT` field flag, value class vs identity class | +| `04-pem-encodings.txt` | JEP 542: `PEMEncoder`/`PEMDecoder` on 28 (no flag) and 25 (preview) | +| `05-json-incubator.txt` | JEP 540: `jdk.incubator.json` | +| `06-shenandoah.txt` | JEP 535 could not be tested on this build (see file) | +| `07-jep-status.txt` | Published JEP status, quoted (not run) | diff --git a/jdk28-preview/output/00-environment.txt b/jdk28-preview/output/00-environment.txt new file mode 100644 index 0000000..6064058 --- /dev/null +++ b/jdk28-preview/output/00-environment.txt @@ -0,0 +1,7 @@ +# 00-environment +--- JDK 28 early-access: +openjdk version "28-ea" 2027-03-23 +OpenJDK 64-Bit Server VM (build 28-ea+17-1282, mixed mode, sharing) +--- JDK 25: +openjdk version "25.0.4.1" 2026-08-18 LTS +OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing) diff --git a/jdk28-preview/output/01-value-classes.txt b/jdk28-preview/output/01-value-classes.txt new file mode 100644 index 0000000..b3cae8f --- /dev/null +++ b/jdk28-preview/output/01-value-classes.txt @@ -0,0 +1,8 @@ +# 01-value-classes: ValueClasses.java on JDK 28 with --enable-preview +p == q: true, equals: true +isValue: true +synchronized on a value object: java.lang.IdentityException: Cannot synchronize on an instance of value class ValueClasses$Point +--- IntegerIdentity.java: same source, three runs +JDK 25: Integer 1000 == Integer 1000: false +JDK 28, no preview: Integer 1000 == Integer 1000: false +JDK 28, --enable-preview: Integer 1000 == Integer 1000: true diff --git a/jdk28-preview/output/02-forgot-the-preview-flag.txt b/jdk28-preview/output/02-forgot-the-preview-flag.txt new file mode 100644 index 0000000..231909e --- /dev/null +++ b/jdk28-preview/output/02-forgot-the-preview-flag.txt @@ -0,0 +1,9 @@ +# 02-forgot-the-preview-flag +--- javac ValueClasses.java (no --enable-preview): +src/ValueClasses.java:3: error: value classes are a preview feature and are disabled by default. + value record Point(int x, int y) {} + ^ + (use --enable-preview to enable value classes) +--- java ValueClasses (compiled with the flag, run without): +Error: LinkageError occurred while loading main class ValueClasses + java.lang.UnsupportedClassVersionError: Preview features are not enabled for ValueClasses (class file version 72.65535). Try running with '--enable-preview' diff --git a/jdk28-preview/output/03-strict-init-flags.txt b/jdk28-preview/output/03-strict-init-flags.txt new file mode 100644 index 0000000..2929ce3 --- /dev/null +++ b/jdk28-preview/output/03-strict-init-flags.txt @@ -0,0 +1,7 @@ +# 03-strict-init-flags (javap -v -p, field flags only) +--- StrictFieldProbe$Money + final long cents; + flags: (0x0810) ACC_FINAL, ACC_STRICT_INIT +--- StrictFieldProbe$Legacy + final long cents; + flags: (0x0010) ACC_FINAL diff --git a/jdk28-preview/output/04-pem-encodings.txt b/jdk28-preview/output/04-pem-encodings.txt new file mode 100644 index 0000000..1af4f05 --- /dev/null +++ b/jdk28-preview/output/04-pem-encodings.txt @@ -0,0 +1,11 @@ +# 04-pem-encodings (JEP 542): PemRoundTrip.java +--- JDK 28, no flags: +-----BEGIN PUBLIC KEY----- +round trip equal: true +--- JDK 25, no flags: +src/PemRoundTrip.java:10: error: PEMEncoder is a preview API and is disabled by default. + String pem = PEMEncoder.of().encodeToString(kp.getPublic()); + ^ +--- JDK 25, --enable-preview: +-----BEGIN PUBLIC KEY----- +round trip equal: true diff --git a/jdk28-preview/output/05-json-incubator.txt b/jdk28-preview/output/05-json-incubator.txt new file mode 100644 index 0000000..d404221 --- /dev/null +++ b/jdk28-preview/output/05-json-incubator.txt @@ -0,0 +1,24 @@ +# 05-json-incubator (JEP 540): JsonDemo.java on JDK 28 +--- module and API surface: +jdk.incubator.json@28-ea +exports jdk.incubator.json +requires java.base mandated + public static jdk.incubator.json.JsonValue parse(java.lang.String); + public static jdk.incubator.json.JsonValue parse(char[]); + public static java.lang.String toDisplayString(jdk.incubator.json.JsonValue, java.lang.String); +--- without --add-modules: +src/JsonDemo.java:1: error: package jdk.incubator.json is not visible +import jdk.incubator.json.*; +--- with --add-modules jdk.incubator.json: +name = Ada +second language = fr +age + 1 = 37 +missing key -> Optional.empty +{ + "name": "Ada", + "langs": [ + "en", + "fr" + ], + "age": 36 +} diff --git a/jdk28-preview/output/06-shenandoah.txt b/jdk28-preview/output/06-shenandoah.txt new file mode 100644 index 0000000..c7e64c1 --- /dev/null +++ b/jdk28-preview/output/06-shenandoah.txt @@ -0,0 +1,8 @@ +# 06-shenandoah (JEP 535: generational mode by default) +--- JDK 28 EA build, -XX:+UseShenandoahGC: +Error occurred during initialization of VM +Option -XX:+UseShenandoahGC not supported +--- JDK 25 Temurin, -XX:+UseShenandoahGC -Xlog:gc*: +[0.007s][info][gc ] Using Shenandoah +[0.009s][info][gc,init] Mode: Snapshot-At-The-Beginning (SATB) +[0.016s][info][gc,metaspace] CDS archive(s) mapped at: [0x0000000020000000-0x0000000020d96000-0x0000000020d96000), size 14245888, SharedBaseAddress: 0x0000000020000000, ArchiveRelocationMode: 1. diff --git a/jdk28-preview/output/07-jep-status.txt b/jdk28-preview/output/07-jep-status.txt new file mode 100644 index 0000000..6bfff17 --- /dev/null +++ b/jdk28-preview/output/07-jep-status.txt @@ -0,0 +1,7 @@ +# 07-jep-status (quoted from published articles on 2026-09-28; NOT produced by running code; status changes weekly) +InfoQ, "JDK 27 and JDK 28: What We Know So Far" (https://www.infoq.com/news/2026/08/java-27-so-far/), August 2026: + Targeted to JDK 28: JEP 541 Deprecate the macOS/x64 Port for Removal; JEP 540 Simple JSON API (Incubator); + JEP 539 Strict Field Initialization in the JVM (Preview); JEP 535 Shenandoah GC: Generational Mode by Default; JEP 401 Value Objects (Preview) + Proposed to target: JEP 542 PEM Encodings of Cryptographic Objects + "Scheduled for a GA release in March 2027" +Inside.java, 2026-09-20 (https://inside.java/2026/09/20/jep401-target-jdk28/): "JEP targeted to JDK 28: 401: Value Objects (Preview)" diff --git a/jdk28-preview/scripts/run-all.sh b/jdk28-preview/scripts/run-all.sh new file mode 100755 index 0000000..72f5551 --- /dev/null +++ b/jdk28-preview/scripts/run-all.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Regenerates output/. Needs JDK 25 and a JDK 28 early-access build (default location below; see output/00-environment.txt). +# Early-access builds change weekly: expect these transcripts to need regenerating, and re-read the post's status callout. +set -uo pipefail +cd "$(dirname "$0")/.." +J28=${JDK28:-/tmp/tools/jdk28}; J25=${JDK25:-/tmp/tools/jdk-25.0.4.1+1}; OUT=$PWD/output; W=/tmp/jdk28-preview-work; rm -rf "$W"; mkdir -p "$W" "$OUT" +q() { grep -v -E '^Picked up'; } +ver() { "$1/bin/java" -version 2>&1 | q | sed -n '1p;3p'; } + +{ echo "# 00-environment"; echo "--- JDK 28 early-access:"; ver "$J28"; echo "--- JDK 25:"; ver "$J25"; } > "$OUT/00-environment.txt" + +{ echo "# 01-value-classes: ValueClasses.java on JDK 28 with --enable-preview" + "$J28/bin/javac" --enable-preview --release 28 -d "$W" src/ValueClasses.java 2>&1 | q | grep -v '^Note:' + "$J28/bin/java" --enable-preview -cp "$W" ValueClasses 2>&1 | q + echo "--- IntegerIdentity.java: same source, three runs" + "$J25/bin/javac" -d "$W/i25" src/IntegerIdentity.java 2>&1 | q; "$J28/bin/javac" --enable-preview --release 28 -d "$W/i28" src/IntegerIdentity.java 2>&1 | q | grep -v '^Note:' + echo "JDK 25: $("$J25/bin/java" -cp "$W/i25" IntegerIdentity 2>&1 | q)" + echo "JDK 28, no preview: $("$J28/bin/java" -cp "$W/i25" IntegerIdentity 2>&1 | q)" + echo "JDK 28, --enable-preview: $("$J28/bin/java" --enable-preview -cp "$W/i28" IntegerIdentity 2>&1 | q)" +} > "$OUT/01-value-classes.txt" + +{ echo "# 02-forgot-the-preview-flag" + echo "--- javac ValueClasses.java (no --enable-preview):"; "$J28/bin/javac" -d "$W/n" src/ValueClasses.java 2>&1 | q | head -4 + echo "--- java ValueClasses (compiled with the flag, run without):"; "$J28/bin/java" -cp "$W" ValueClasses 2>&1 | q | head -2 +} > "$OUT/02-forgot-the-preview-flag.txt" + +"$J28/bin/javac" --enable-preview --release 28 -d "$W/s" src/StrictFieldProbe.java 2>&1 | q >/dev/null +{ echo "# 03-strict-init-flags (javap -v -p, field flags only)" + for c in 'StrictFieldProbe$Money' 'StrictFieldProbe$Legacy'; do echo "--- $c"; "$J28/bin/javap" -v -p -cp "$W/s" "$c" 2>&1 | q | grep -A2 -E '^ (final )?long cents;' | grep -E 'cents|flags' ; done +} > "$OUT/03-strict-init-flags.txt" + +{ echo "# 04-pem-encodings (JEP 542): PemRoundTrip.java" + echo "--- JDK 28, no flags:"; "$J28/bin/javac" -d "$W/p28" src/PemRoundTrip.java 2>&1 | q; "$J28/bin/java" -cp "$W/p28" PemRoundTrip 2>&1 | q + echo "--- JDK 25, no flags:"; "$J25/bin/javac" -d "$W/p25" src/PemRoundTrip.java 2>&1 | q | head -3 + echo "--- JDK 25, --enable-preview:"; "$J25/bin/javac" --enable-preview --release 25 -d "$W/p25" src/PemRoundTrip.java 2>&1 | q | grep -v '^Note:'; "$J25/bin/java" --enable-preview -cp "$W/p25" PemRoundTrip 2>&1 | q +} > "$OUT/04-pem-encodings.txt" + +{ echo "# 05-json-incubator (JEP 540): JsonDemo.java on JDK 28" + echo "--- module and API surface:"; "$J28/bin/java" --describe-module jdk.incubator.json 2>&1 | q | head -3 + "$J28/bin/javap" --module jdk.incubator.json -public jdk.incubator.json.Json 2>&1 | q | grep -E 'parse|toDisplay' + echo "--- without --add-modules:"; "$J28/bin/javac" -d "$W/j" src/JsonDemo.java 2>&1 | q | head -2 + echo "--- with --add-modules jdk.incubator.json:" + "$J28/bin/javac" --add-modules jdk.incubator.json -d "$W/j" src/JsonDemo.java 2>&1 | q | grep -v -E '^(Note:|warning:|1 warning)' + "$J28/bin/java" --add-modules jdk.incubator.json -cp "$W/j" JsonDemo 2>&1 | q | grep -v '^WARNING' +} > "$OUT/05-json-incubator.txt" + +{ echo "# 06-shenandoah (JEP 535: generational mode by default)" + echo "--- JDK 28 EA build, -XX:+UseShenandoahGC:"; "$J28/bin/java" -XX:+UseShenandoahGC -Xlog:gc=info -version 2>&1 | q | head -2 + echo "--- JDK 25 Temurin, -XX:+UseShenandoahGC -Xlog:gc*:"; "$J25/bin/java" -XX:+UseShenandoahGC -Xlog:gc*=info -version 2>&1 | q | grep -E 'Using Shenandoah|Mode:' +} > "$OUT/06-shenandoah.txt" + + +cat > "$OUT/07-jep-status.txt" <<'EOT' +# 07-jep-status (quoted from published articles on 2026-09-28; NOT produced by running code; status changes weekly) +InfoQ, "JDK 27 and JDK 28: What We Know So Far" (https://www.infoq.com/news/2026/08/java-27-so-far/), August 2026: + Targeted to JDK 28: JEP 541 Deprecate the macOS/x64 Port for Removal; JEP 540 Simple JSON API (Incubator); + JEP 539 Strict Field Initialization in the JVM (Preview); JEP 535 Shenandoah GC: Generational Mode by Default; JEP 401 Value Objects (Preview) + Proposed to target: JEP 542 PEM Encodings of Cryptographic Objects + "Scheduled for a GA release in March 2027" +Inside.java, 2026-09-20 (https://inside.java/2026/09/20/jep401-target-jdk28/): "JEP targeted to JDK 28: 401: Value Objects (Preview)" +EOT +ls -1 "$OUT" diff --git a/jdk28-preview/src/IntegerIdentity.java b/jdk28-preview/src/IntegerIdentity.java new file mode 100644 index 0000000..1e9a5ff --- /dev/null +++ b/jdk28-preview/src/IntegerIdentity.java @@ -0,0 +1,7 @@ +/** Compiles on every JDK. Does == on two boxed 1000s say true? */ +public class IntegerIdentity { + public static void main(String[] args) { + Integer a = 1000, b = 1000; + System.out.println("Integer 1000 == Integer 1000: " + (a == b)); + } +} diff --git a/jdk28-preview/src/JsonDemo.java b/jdk28-preview/src/JsonDemo.java new file mode 100644 index 0000000..900a79d --- /dev/null +++ b/jdk28-preview/src/JsonDemo.java @@ -0,0 +1,13 @@ +import jdk.incubator.json.*; + +/** JEP 540: the incubating JSON API in module jdk.incubator.json. Needs --add-modules jdk.incubator.json. */ +public class JsonDemo { + public static void main(String[] args) { + JsonValue doc = Json.parse("{\"name\":\"Ada\",\"langs\":[\"en\",\"fr\"],\"age\":36}"); + System.out.println("name = " + doc.get("name").asString()); + System.out.println("second language = " + doc.get("langs").get(1).asString()); + System.out.println("age + 1 = " + (doc.get("age").asInt() + 1)); + System.out.println("missing key -> " + doc.tryGet("email")); + System.out.println(Json.toDisplayString(doc, " ")); + } +} diff --git a/jdk28-preview/src/PemRoundTrip.java b/jdk28-preview/src/PemRoundTrip.java new file mode 100644 index 0000000..2e60d3c --- /dev/null +++ b/jdk28-preview/src/PemRoundTrip.java @@ -0,0 +1,15 @@ +import java.security.*; +import java.security.spec.ECGenParameterSpec; + +/** JEP 542: PEMEncoder / PEMDecoder. Preview in 25; does it still need --enable-preview on 28? */ +public class PemRoundTrip { + public static void main(String[] args) throws Exception { + KeyPairGenerator g = KeyPairGenerator.getInstance("EC"); + g.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = g.generateKeyPair(); + String pem = PEMEncoder.of().encodeToString(kp.getPublic()); + System.out.println(pem.lines().findFirst().get()); + PublicKey back = PEMDecoder.of().decode(pem, PublicKey.class); + System.out.println("round trip equal: " + back.equals(kp.getPublic())); + } +} diff --git a/jdk28-preview/src/StrictFieldProbe.java b/jdk28-preview/src/StrictFieldProbe.java new file mode 100644 index 0000000..b1f578d --- /dev/null +++ b/jdk28-preview/src/StrictFieldProbe.java @@ -0,0 +1,6 @@ +/** JEP 539 (preview in JDK 28): a JVM field flag. javac sets it on value-class fields; an identity class is the control. */ +public class StrictFieldProbe { + static value class Money { final long cents; Money(long c) { cents = c; } } + static class Legacy { final long cents; Legacy(long c) { cents = c; } } + public static void main(String[] args) { new Money(1); new Legacy(1); } +} diff --git a/jdk28-preview/src/ValueClasses.java b/jdk28-preview/src/ValueClasses.java new file mode 100644 index 0000000..88733cf --- /dev/null +++ b/jdk28-preview/src/ValueClasses.java @@ -0,0 +1,16 @@ +/** JEP 401 (preview in JDK 28): value classes and value records give up identity, so == compares state. */ +public class ValueClasses { + value record Point(int x, int y) {} + + public static void main(String[] args) { + Point p = new Point(1, 2), q = new Point(1, 2); + System.out.println("p == q: " + (p == q) + ", equals: " + p.equals(q)); + System.out.println("isValue: " + p.getClass().isValue()); + Object o = p; + try { + synchronized (o) { System.out.println("locked"); } + } catch (Throwable t) { + System.out.println("synchronized on a value object: " + t); + } + } +}