Add jdk28-preview module: JDK 28 early-access build, value classes, strict init flag, PEM, JSON incubator

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Gth99spB1CzQmBFofXmU4Z
This commit is contained in:
Claude
2026-09-28 08:48:04 +00:00
parent 57a04dc393
commit c8ff3acf09
15 changed files with 221 additions and 0 deletions
+21
View File
@@ -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) |
+7
View File
@@ -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)
@@ -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
@@ -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'
@@ -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
+11
View File
@@ -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
@@ -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
}
+8
View File
@@ -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.
+7
View File
@@ -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)"
+62
View File
@@ -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"
+7
View File
@@ -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));
}
}
+13
View File
@@ -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, " "));
}
}
+15
View File
@@ -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()));
}
}
+6
View File
@@ -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); }
}
+16
View File
@@ -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);
}
}
}