Saturday, June 06, 2026

Java 26: Mutating Final Fields Using Reflection

Java 26 introduces a new warning when code uses reflection to mutate final fields. This change comes from JEP 500: Prepare to Make Final Mean Final, and is part of Java’s ongoing move toward stronger integrity and better JVM optimisations. The JVM relies on the assumption that final fields never change in order to perform optimisations such as constant folding and safe object initialisation in concurrent code. However, deep reflection APIs like Field.setAccessible(true) and Field.set(...) have historically allowed any code to mutate final fields at runtime, breaking those guarantees.

Consider the following code that reflectively mutates a final field:

public class Foo {

  final int x;

  Foo() {
    x = 100;
  }

  public static void main(String... args) 
                          throws Exception {

    Foo obj = new Foo();

    System.out.println(obj.x);
    
    java.lang.reflect.Field f =
            Foo.class.getDeclaredField("x");
    f.setAccessible(true);
    f.set(obj, 200); // mutate the final field

    System.out.println(obj.x);
  }
}

In older JDK versions, this would silently mutate the supposedly immutable final field.

In JDK 26, it still works, but produces warnings:

100
WARNING: Final field x in class Foo has been mutated reflectively by class Foo in unnamed module @764c12b6
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
200

In a future JDK release, these operations are expected to fail unless explicitly enabled using --enable-final-field-mutation=ALL-UNNAMED or for specific modules --enable-final-field-mutation=M1,M2.

So, stop mutating final fields! final means final.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.