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:
@@ -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) |
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
@@ -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)"
|
||||||
Executable
+62
@@ -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"
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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, " "));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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); }
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user