From ed9cde01f7b8b86ab075ec3969e3251562bc1d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alois=20Se=C4=8Dk=C3=A1r?= Date: Fri, 23 Oct 2026 17:34:21 +0200 Subject: [PATCH] feat(jdk26): add demo for JEP 500 --- .../org/javademos/init/Java26DemoLoader.java | 60 +++++----- .../java26/jep500/FinalMeanFinalDemo.java | 94 ++++++++++++++++ src/main/resources/JDK26Info.json | 106 ++++++++++-------- 3 files changed, 182 insertions(+), 78 deletions(-) create mode 100644 src/main/java/org/javademos/java26/jep500/FinalMeanFinalDemo.java diff --git a/src/main/java/org/javademos/init/Java26DemoLoader.java b/src/main/java/org/javademos/init/Java26DemoLoader.java index 2e524d2..3abd42f 100644 --- a/src/main/java/org/javademos/init/Java26DemoLoader.java +++ b/src/main/java/org/javademos/init/Java26DemoLoader.java @@ -1,29 +1,31 @@ -package org.javademos.init; - -import java.util.Map; - -import org.javademos.commons.IDemo; -import org.javademos.commons.IDemoLoader; - -import org.javademos.java26.jep517.Http3Demo; -import org.javademos.java26.jep522.ReduceSyncDemo; -import org.javademos.java26.jep524.PemEncodingsDemo; -import org.javademos.java26.jep525.StructuredConcurrencyDemo; -import org.javademos.java26.jep526.LazyConstantsDemo; -import org.javademos.java26.jep529.VectorAPIDemo; - -/** - * Loads demos for Java 26. - */ -public class Java26DemoLoader implements IDemoLoader { - - @Override - public void loadDemos(Map demos) { - demos.put(517, new Http3Demo()); - demos.put(522, new ReduceSyncDemo()); - demos.put(524, new PemEncodingsDemo()); - demos.put(525, new StructuredConcurrencyDemo()); - demos.put(526, new LazyConstantsDemo()); - demos.put(529, new VectorAPIDemo()); - } -} +package org.javademos.init; + +import java.util.Map; + +import org.javademos.commons.IDemo; +import org.javademos.commons.IDemoLoader; + +import org.javademos.java26.jep500.FinalMeanFinalDemo; +import org.javademos.java26.jep517.Http3Demo; +import org.javademos.java26.jep522.ReduceSyncDemo; +import org.javademos.java26.jep524.PemEncodingsDemo; +import org.javademos.java26.jep525.StructuredConcurrencyDemo; +import org.javademos.java26.jep526.LazyConstantsDemo; +import org.javademos.java26.jep529.VectorAPIDemo; + +/** + * Loads demos for Java 26. + */ +public class Java26DemoLoader implements IDemoLoader { + + @Override + public void loadDemos(Map demos) { + demos.put(500, new FinalMeanFinalDemo()); + demos.put(517, new Http3Demo()); + demos.put(522, new ReduceSyncDemo()); + demos.put(524, new PemEncodingsDemo()); + demos.put(525, new StructuredConcurrencyDemo()); + demos.put(526, new LazyConstantsDemo()); + demos.put(529, new VectorAPIDemo()); + } +} diff --git a/src/main/java/org/javademos/java26/jep500/FinalMeanFinalDemo.java b/src/main/java/org/javademos/java26/jep500/FinalMeanFinalDemo.java new file mode 100644 index 0000000..04ce61c --- /dev/null +++ b/src/main/java/org/javademos/java26/jep500/FinalMeanFinalDemo.java @@ -0,0 +1,94 @@ +package org.javademos.java26.jep500; + +import java.lang.reflect.Field; + +import org.javademos.commons.IDemo; + +/// Demo for JDK 26 feature JEP 500 - Prepare to Make Final Mean Final. +/// +/// Since JDK 5, deep reflection (`Field.setAccessible` + `Field.set`) can reassign `final` fields. +/// JDK 26 starts the process of disallowing that - by default, such a mutation still succeeds, +/// but the JVM prints a warning. A future release will throw `IllegalAccessException` instead. +/// +/// Relevant JVM options: +/// - `--enable-final-field-mutation=ALL-UNNAMED` (or `=M1,M2` for named modules) +/// explicitly allows the mutation for the selected code and silences the warning +/// (executable JARs may use the `Enable-Final-Field-Mutation: ALL-UNNAMED` manifest entry) +/// - `--illegal-final-field-mutation=allow|warn|debug|deny` controls what happens +/// on an illegal mutation (`warn` is the default in JDK 26, `deny` will be the future default) +/// +/// ### JEP history +/// - JDK 26: [JEP 500 - Prepare to Make Final Mean Final](https://openjdk.org/jeps/500) +/// +/// ### Further reading +/// - [Integrity by Default](https://openjdk.org/jeps/8305968) +/// - [Field.set Javadoc](https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/lang/reflect/Field.html#set(java.lang.Object,java.lang.Object)) +/// +/// @author Alois Sečkár +public class FinalMeanFinalDemo implements IDemo { + + @Override + public void demo() { + info(500); + + // 1) a final field in a normal class + // since JDK 5 deep reflection was allowed to reassign it + // in JDK 26 it still works, but the JVM issues a warning to the standard error stream: + // WARNING: Final field value in ...Holder has been mutated reflectively by class ...FinalMeanFinalDemo ... + // WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning + // (the warning is printed at most once per module, so it may be missing if other code + // in the unnamed module already triggered it before) + // with `--illegal-final-field-mutation=deny`, `Field.set` throws `IllegalAccessException` + var holder = new Holder(100); + System.out.println("Holder value before mutation: " + holder.value); + try { + Field field = Holder.class.getDeclaredField("value"); + field.setAccessible(true); // the behavior of `setAccessible` itself is unchanged + field.set(holder, 200); // this is where JDK 26 checks the new restrictions + System.out.println("Holder value after mutation: " + holder.value + " (check stderr for the warning)"); + } catch (ReflectiveOperationException ex) { + System.out.println("Final field mutation denied: " + ex); + } + + // 2) record components were never mutable via deep reflection (since JDK 16) + // JEP 500 aims to align final fields in normal classes with this behavior + var point = new Point(1, 2); + try { + Field field = Point.class.getDeclaredField("x"); + field.setAccessible(true); + field.set(point, 42); + } catch (ReflectiveOperationException ex) { + System.out.println("Record field mutation denied: " + ex.getClass().getSimpleName()); + } + + // 3) the recommended way - do not mutate, create a new instance with the desired value instead + // (e.g. via constructor, static factory or "wither" method) + var updated = holder.withValue(300); + System.out.println("New Holder created instead of mutation: " + updated.value); + + // Notes: + // - to find the code mutating final fields, run with `--illegal-final-field-mutation=debug` + // (prints a stack trace for every mutation) or record a JFR file and look for + // `jdk.FinalFieldMutation` events: `jfr print --events jdk.FinalFieldMutation recording.jfr` + // - serialization libraries should use `sun.reflect.ReflectionFactory` instead of deep reflection + // - `MethodHandles.Lookup.unreflectSetter` on a final field is restricted the same way as `Field.set` + // - mutating final fields via JNI `SetField` is undefined behavior; `-Xcheck:jni` now warns about it + // - once final means final, the JVM may trust final fields and apply optimizations like constant folding + + System.out.println(); + } + + static class Holder { + final int value; + + Holder(int value) { + this.value = value; + } + + Holder withValue(int newValue) { + return new Holder(newValue); + } + } + + record Point(int x, int y) {} +} diff --git a/src/main/resources/JDK26Info.json b/src/main/resources/JDK26Info.json index 20b6c60..77dc04b 100644 --- a/src/main/resources/JDK26Info.json +++ b/src/main/resources/JDK26Info.json @@ -1,50 +1,58 @@ -[ - { - "jep": 517, - "jdk": 26, - "name": "JEP 517 - HTTP/3 for the HTTP Client API", - "dscr": "Update the HTTP Client API to support the HTTP/3 protocol, so that libraries and applications can interact with HTTP/3 servers with minimal code change.", - "link": false, - "code": true - }, - { - "jep": 522, - "jdk": 26, - "name":"JEP 522 - G1 GC: Improve Throughput by Reducing Synchronization", - "dscr": "Improve latency and throughput by reducing the amount of synchronization required between application threads and GC threads", - "link": false, - "code": false - }, - { - "jep": 524, - "jdk": 26, - "name": "JEP 524 - PEM Encodings of Cryptographic Objects (Second Preview)", - "dscr": "Up-to-date demo in `org.javademos.java27.jep538.PemEncodingsDemo`.", - "link": true, - "code": false - }, - { - "jep": 525, - "jdk": 26, - "name": "JEP 525 - Structured Concurrency (Sixth Preview)", - "dscr": "Up-to-date demo in `org.javademos.java27.jep533.StructuredConcurrencyDemo`.", - "link": true, - "code": false - }, - { - "jep": 526, - "jdk": 26, - "name": "JEP 526 - Lazy Constants (Preview)", - "dscr": "Up-to-date demo in `org.javademos.java27.jep531.LazyConstantsDemo`.", - "link": true, - "code": false - }, - { - "jep": 529, - "jdk": 26, - "name": "JEP 529 - Vector API (Eleventh Incubator)", - "dscr": "Up-to-date demo in `org.javademos.java27.jep537.VectorAPIDemo`.", - "link": true, - "code": false - } +[ + { + "jep": 500, + "jdk": 26, + "name": "JEP 500 - Prepare to Make Final Mean Final", + "dscr": "Issue warnings about uses of deep reflection to mutate final fields to prepare for a future release that restricts final field mutation by default.", + "link": false, + "code": true + }, + { + "jep": 517, + "jdk": 26, + "name": "JEP 517 - HTTP/3 for the HTTP Client API", + "dscr": "Update the HTTP Client API to support the HTTP/3 protocol, so that libraries and applications can interact with HTTP/3 servers with minimal code change.", + "link": false, + "code": true + }, + { + "jep": 522, + "jdk": 26, + "name":"JEP 522 - G1 GC: Improve Throughput by Reducing Synchronization", + "dscr": "Improve latency and throughput by reducing the amount of synchronization required between application threads and GC threads", + "link": false, + "code": false + }, + { + "jep": 524, + "jdk": 26, + "name": "JEP 524 - PEM Encodings of Cryptographic Objects (Second Preview)", + "dscr": "Up-to-date demo in `org.javademos.java27.jep538.PemEncodingsDemo`.", + "link": true, + "code": false + }, + { + "jep": 525, + "jdk": 26, + "name": "JEP 525 - Structured Concurrency (Sixth Preview)", + "dscr": "Up-to-date demo in `org.javademos.java27.jep533.StructuredConcurrencyDemo`.", + "link": true, + "code": false + }, + { + "jep": 526, + "jdk": 26, + "name": "JEP 526 - Lazy Constants (Preview)", + "dscr": "Up-to-date demo in `org.javademos.java27.jep531.LazyConstantsDemo`.", + "link": true, + "code": false + }, + { + "jep": 529, + "jdk": 26, + "name": "JEP 529 - Vector API (Eleventh Incubator)", + "dscr": "Up-to-date demo in `org.javademos.java27.jep537.VectorAPIDemo`.", + "link": true, + "code": false + } ] \ No newline at end of file