Preparing for final Field Mutation Restrictions
In the Java programming language, fields declared as final
cannot be mutated, that is, assigned after initialization. However, some Java SE APIs
can enable the mutation of final fields at run time. In particular,
the AccessibleObject.setAccessible(boolean) method in the java.lang.reflect package enables deep reflection,
which is reflection over fields and methods without regard to encapsulation boundaries.
Any code can use it to invoke the private methods of any class, read
and write the private fields of any object, and even write to
final fields.
Starting in JDK 26, the JDK generates a warning similar to the following for
the first mutation of a final field through deep reflection.
WARNING: Final field configValue in class com.example.InternalService
has been mutated reflectively by class com.example.bean.BeanPropertyField
in unnamed module @c387f44 (file:/usr/lib/library-utils.jar)
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 enabledThe warning message indicates the field that has been mutated and the code that mutated the field.
In a future release, the JDK will instead throw an exception. Consequently,
to prepare for these restrictions, you can selectively enable the ability to mutate
final fields through deep reflection where essential.
See JEP 500: Prepare to Make Final Mean Final.
The --enable-final-field-mutation Command-Line Option
Starting in JDK 26, a warning is generated for the first mutation of a final field through deep reflection.
You can suppress these warnings by specifying the
--enable-final-field-mutation command-line option, which
enables code to mutate final instance fields through deep
reflection when the field's declaring class is in a package that's open to the
module mutating the field.
To enable code on the class path to mutate final fields through deep reflection, specify the value ALL-UNNAMED for this option. The following command suppresses the final field mutation warnings:
java --enable-final-field-mutation=ALL-UNNAMED Application
To enable code on the module path to mutate final
fields through deep reflection, specify the modules in a comma-separated list, for
example:
java --enable-final-field-mutation=MyModule1,MyModule2,MyModule3 MyModularApp
The --illegal-final-field-mutation Command-Line Option
Alternatively, you can specify the command-line option --illegal-final-field-mutation, which affects all code in the system and not just code on the code path or in specific modules. In addition, this option handles final field mutations differently depending on the value assigned to it:
-
--illegal-final-field-mutation=allowallowsfinalfield mutations through deep reflection to proceed without warning. -
--illegal-final-field-mutation=warnallowsfinalfield mutation through deep reflection but issues a warning the first time that code in a particular module performs such a mutation. At most one warning per module is issued.Starting in JDK 26, this is the default mode. This mode will be phased out and removed in a future release.
-
--illegal-final-field-mutation=debugis identical towarnexcept both a warning message and a stack trace are issued for everyfinalfield mutation through deep reflection. -
--illegal-final-field-mutation=denyresults inField::setthrowing an IllegalAccessException for everyfinalfield mutation through deep reflection.This mode will become the default in a future release.
Tip:
Test your code with--illegal-final-field-mutation=deny to ensure that it continues
to work after the JDK denies final field mutation by default in a
future release.
Which Option to Use, --illegal-final-field-mutation or --enable-final-field-mutation?
Use the --illegal-final-field-mutation option to help you identify which libraries mutate final fields through deep reflection so that you can ideally update the libraries to newer versions that don't mutate final fields.
Use the --enable-final-field-mutation option if you use libraries that mutate final fields through deep reflection, and you are unable to remove, replace, or modify these libraries.
Note:
- It's recommended that you use neither
--illegal-final-field-mutationnor--enable-final-field-mutation. Using either option means your application still depends on libraries that mutatefinalfields through deep reflection. This is potentially insecure, so it's preferable to remove, replace, or modify the libraries identified by JDK warnings. - It's recommended that you use
--illegal-final-field-mutation=allowonly as a temporary measure while you decide how to handlefinalfield mutation in your application.
Identifying Code That Mutates final Fields Through Deep Reflection
Code that mutates final fields through deep reflection
is usually library code, not application code. You can identify precisely which code
mutates final fields through deep reflection by starting the Java
runtime with the command-line option
--illegal-final-field-mutation=debug or with JDK Flight
Recorder (JFR) enabled.
The following example runs a Java application with the --illegal-final-field-mutation=debug command-line option. A warning and a stack trace are issued for every final field mutation through deep reflection.
$ java --illegal-final-field-mutation=debug Application
...
Final field maxCapacity in class com.example.ResourceLibrary$Storage has been mutated
reflectively by class com.example.ResourceLibrary in unnamed module @3d4eac69
(file:/usr/lib/UtilLibrary.jar)
at java.base/java.lang.reflect.Field.postSetFinal(Field.java:1517)
at java.base/java.lang.reflect.Field.setFinal(Field.java:1431)
at java.base/java.lang.reflect.Field.set(Field.java:891)
at com.example.ResourceLibrary.addUnit(ResourceLibrary.java:24)
at Application.main(Application.java:34)In this example, a method in com.example.ResourceLibrary mutates a final field. When JFR is enabled, the JVM records a jdk.FinalFieldMutation event whenever code mutates a final instance field through deep reflection or uses Lookup::unreflectSetter to get a MethodHandle with write access to a reflected final field. This event identifies the class declaring the final field and the name of the final field. The event also generates a stack trace to show where the final field mutation is coming from.
The following example runs a Java application with JFR enabled and then displays jdk.FinalFieldMutation events in the JFR recording:
$ java -XX:StartFlightRecording:filename=recording.jfr --enable-final-field-mutation=ALL-UNNAMED Application
[0.917s][info][jfr,startup] Started recording 1. No limit specified, using maxsize=250MB as default.
[0.917s][info][jfr,startup]
[0.917s][info][jfr,startup] Use jcmd 5102 JFR.dump name=1 to copy recording data to file.
...
$ jfr print --events jdk.FinalFieldMutation recording.jfr
jdk.FinalFieldMutation {
startTime = 23:08:26.089 (2026-01-14)
declaringClass = com.example.ResourceLibrary$Storage (classLoader = app)
fieldName = "maxCapacity"
eventThread = "main" (javaThreadId = 3)
stackTrace = [
java.lang.reflect.Field.set(Object, Object) line: 891
com.example.ResourceLibrary.addUnit() line: 24
Application.main(String[]) line: 34
]
}