JDK-8353836 : Implement JEP 500: Prepare to Make Final Mean Final
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Priority: P4
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 26
  • Submitted: 2025-04-07
  • Updated: 2025-11-18
  • Resolved: 2025-11-18
Related Reports
CSR :  
Description
Summary
-------

By default, emit a warning when mutating a `final` field using reflection unless the mutating module has been granted the capability to do so via a command-line option. The behavior of emitting a warning can be strengthened to throw an exception instead (which will become the default in a future JDK release). Alternatively, the warning can be avoided (an option that will be removed in a future release).

Problem
-------

`java.lang.reflect.Field::setAccessible` makes it possible to mutate `final` instance fields, provided that the package containing the field's class is open to the caller. Because every module is open to itself, the JVM cannot trust that any `final` instance field is immutable, making it hard for it to apply optimizations such as constant folding.

Mutation of `final` instance fields has been allowed since JDK 5, see JDK-5044412.

Solution
--------

Mutating a `final` instance field with `Field::set` is legal, and succeeds without warning, if and only if:

1. `setAccessible(true)` has succeeded for the Field object,
2. the field's class is in a package that is open to the module performing the mutation, and
3. the module performing the mutation has been granted the capability to mutate final fields reflectively via `--enable-final-field-mutation=...`.

All other attempts to mutate a `final` field via `Field::set` are deemed illegal. The behavior of the JDK for illegal `final` field mutation (assuming `setAccessible(true)` has succeeded) is determined by the flag `--illegal-final-field-mutation`. The default value of the flag is `warn`, which allows the field mutation to succeed but emits a warning (at most one per caller's module). Future JDK releases will change the default of this flag and/or restrict its allowed values, or even remove it altogether.

Conditions 2 and 3 above are new. They introduce three incompatibilities, all of which are expected to be rare:

1. If `setAccessible(true)` is called from module X, and the `Field` object is then passed to module Y, then Y calling `Field::set` will fail for a `final` field if the package is not open to Y. This is necessary to preserve strong encapsulation. It is not sufficient merely to enable `final` field mutation for Y in this case.
2. If a package is opened reflectively to Z via `Module::addOpens`, then Z can call `setAccessible(true)` for a `final` field in the package but calling `Field::set` will fail even if `final` field mutation is enabled for Z. This is a design decision to preserve the traceability of code which can mutate `final` fields.
3. If native code in a JNI-attached thread with no Java frames on the stack obtains a `Field` object for which `setAccessible(true)` succeeded, then calling `Field::set` will fail if the package is not exported to all modules. This is a design decision to prevent native code from undermining strong encapsulation.

There are no changes to the specification of the Java Native Interface except for a technical clarification to mention `final` instance fields for completeness. Native code, if allowed to run by `--enable-native-access`, can mutate `final` instance fields via the JNI as in prior JDK releases, except for the corner-case incompatibility noted above.

There are no changes to the specification of the Instrumentation API, which means that agents, if allowed to run by `-javaagent` or `-XX:+EnableDynamicAgentLoading`, can perform exactly the same actions w.r.t. `final` fields as in prior JDK releases.

Specification
-------------

The Java Platform Specification gates the ability of `Field::set` to mutate a `final` field on whether the end user chose to enable "final field mutation" at startup. This is specified in a new new section in "5. Features", after the existing "Restricted Methods" section:

```
* Integrity of final fields

Various methods in the Java SE API provide _write access_ to final
fields at run time. This means that Java code can mutate the value of a
final field after the field has been initialized in a constructor,
an instance initializer, or the field's declaration. Only the values of
final instance fields, not final static fields, can be mutated in this way.

The methods that provide write access, known as _mutation
methods_, are:

- java.lang.reflect.Field::set(Object, Object)
- java.lang.reflect.Field::setBoolean(Object, boolean)
- java.lang.reflect.Field::setByte(Object, byte)
- java.lang.reflect.Field::setChar(Object, char)
- java.lang.reflect.Field::setShort(Object, short)
- java.lang.reflect.Field::setInt(Object, int)
- java.lang.reflect.Field::setLong(Object, long)
- java.lang.reflect.Field::setFloat(Object, float)
- java.lang.reflect.Field::setDouble(Object, double)
- java.lang.invoke.MethodHandles.Lookup::unreflectSetter(Field)

The use of mutation methods to alter the values of final fields is a
legacy practice. It is strongly inadvisable because it undermines the
correctness of programs written in expectation of final fields being
immutable.

In Java SE 26 and later, when a mutation method is invoked, one of the
preconditions for providing its caller with write access is that
_final field mutation_ is enabled for the caller. If final field
mutation is disabled for the caller, then write access is not provided
and the mutation method throws IllegalAccessException.

An Implementation of this Specification must:

- disable final field mutation for all code by default.

- provide a means to invoke its run-time system with final field
  mutation enabled for code identified to the run-time system, and
  disabled for all other code.

(The Reference Implementation provides this capability via the
 command-line option --enable-final-field-mutation=M, where M is a
 comma-separated list of modules. The special operand ALL-UNNAMED
 indicates every unnamed module, which includes code on the class
 path.)

As an aid to migration, an Implementation may allow code to alter the
values of final fields by invoking mutation methods, even if the code
is not identified to the run-time system as being enabled for final
field mutation. In particular:

- An Implementation may provide a means to invoke its run-time system
  with final field mutation enabled for all code. If it does so then
  it must, further, provide a means to invoke its run-time system with
  final field mutation disabled for all code.

(The Reference Implementation provides the first capability via the
 command-line option --illegal-final-field-mutation=allow, and the
 second capability via the command-line option
 --illegal-final-field-mutation=deny.)

- An Implementation may provide a means to invoke its run-time system
  such that all code is provided with write access to final fields. If
  the run-time system is invoked in this way, and if by doing so some
  invocations of mutation methods succeed where otherwise they would
  have failed (because final field mutation was disabled for the
  caller or because some other precondition for write access did not
  hold), then the first such invocation must cause a warning to be
  issued on the standard error stream.

(The Reference Implementation provides this capability via the
 command-line option --illegal-final-field-mutation=warn. At most one
 warning is issued for each module whose code invokes mutation
 methods.)

Future revisions of this Specification are expected to remove these
migration aids.
```
`AccessibleObject.setAccessible(boolean)` is updated to drop the following text that was inherited into `Method` and `Constructor`:
```
-     * <p> This method cannot be used to enable {@linkplain Field#set <em>write</em>}
-     * access to a <em>non-modifiable</em> final field.  The following fields
-     * are non-modifiable:
-     * <ul>
-     * <li>static final fields declared in any class or interface</li>
-     * <li>final fields declared in a {@linkplain Class#isHidden() hidden class}</li>
-     * <li>final fields declared in a {@linkplain Class#isRecord() record}</li>
-     * </ul>
-     * <p> The {@code accessible} flag when {@code true} suppresses Java language access
-     * control checks to only enable {@linkplain Field#get <em>read</em>} access to
-     * these non-modifiable final fields.
```

`Field.setAccessible(boolean)` is updated to expand the text on when write access is enabled:
```
+     * <p>If this reflected object represents a non-final field, and this method is
+     * used to enable access, then both <em>{@linkplain #get(Object) read}</em>
+     * and <em>{@linkplain #set(Object, Object) write}</em> access to the field
+     * are enabled.
+     *
+     * <p>If this reflected object represents a <em>non-modifiable</em> final field
+     * then enabling access only enables read access. Any attempt to {@linkplain
+     * #set(Object, Object) set} the field value throws an {@code
+     * IllegalAccessException}. The following fields are non-modifiable:
+     * <ul>
+     * <li>static final fields declared in any class or interface</li>
+     * <li>final fields declared in a {@linkplain Class#isRecord() record}</li>
+     * <li>final fields declared in a {@linkplain Class#isHidden() hidden class}</li>
+     * </ul>
+     * <p>If this reflected object represents a non-static final field in a normal
+     * class (not a record class or hidden class) then enabling access will enable read
+     * access. Whether write access is allowed or not is checked when attempting to
+     * {@linkplain #set(Object, Object) set} the field value.
```

The set of conditions specified by `Field.set(Object, Object)` for write access is updated..

```
-     * <p>If the underlying field is final, this {@code Field} object has
-     * <em>write</em> access if and only if the following conditions are met:
+     * <p>If the underlying field is final, this {@code Field} object has <em>write</em>
+     * access if and only if all following conditions are true, where {@code D} is
+     * the field's {@linkplain #getDeclaringClass() declaring class}:
+     *
+     * <ul>
+     * <li>{@link #setAccessible(boolean) setAccessible(true)} has succeeded for this
+     *     {@code Field} object.</li>
+     * <li><a href="doc-files/MutationMethods.html">final field mutation is enabled</a>
+     *     for the caller's module.</li>
+     * <li> At least one of the following conditions holds:
+     *     <ol type="a">
+     *     <li> {@code D} and the caller class are in the same module.</li>
+     *     <li> The field is {@code public} and {@code D} is {@code public} in a package
+     *     that the module containing {@code D} exports to at least the caller's module.</li>
+     *     <li> {@code D} is in a package that is {@linkplain Module#isOpen(String, Module)
+     *     open} to the caller's module.</li>
+     *     </ol>
+     * </li>
+     * <li>{@code D} is not a {@linkplain Class#isRecord() record class}.</li>
+     * <li>{@code D} is not a {@linkplain Class#isHidden() hidden class}.</li>
+     * <li>The field is non-static.</li>
+     * </ul>
+     *
+     * <p>If any of the above conditions is not met, this method throws an
+     * {@code IllegalAccessException}.
+     *
+     * <p>These conditions are more restrictive than the conditions specified by {@link
+     * #setAccessible(boolean)} to suppress access checks. In particular, updating a
+     * module to export or open a package cannot be used to allow <em>write</em> access
+     * to final fields with the {@code set} methods defined by {@code Field}.
+     * Condition (b) is not met if the module containing {@code D} has been updated with
+     * {@linkplain Module#addExports(String, Module) addExports} to export the package to
+     * the caller's module. Condition (c) is not met if the module containing {@code D}
+     * has been updated with {@linkplain Module#addOpens(String, Module) addOpens} to open
+     * the package to the caller's module.
+     *
+     * <p>This method may be called by <a href="{@docRoot}/../specs/jni/index.html">
+     * JNI code</a> with no caller class on the stack. In that case, and when the
+     * underlying field is final, this {@code Field} object has <em>write</em> access
+     * if and only if all following conditions are true, where {@code D} is the field's
+     * {@linkplain #getDeclaringClass() declaring class}:
+     *
      * <ul>
-     * <li>{@link #setAccessible(boolean) setAccessible(true)} has succeeded for
-     *     this {@code Field} object;</li>
-     * <li>the field is non-static; and</li>
-     * <li>the field's declaring class is not a {@linkplain Class#isHidden()
-     *     hidden class}; and</li>
-     * <li>the field's declaring class is not a {@linkplain Class#isRecord()
-     *     record class}.</li>
+     * <li>{@code setAccessible(true)} has succeeded for this {@code Field} object.</li>
+     * <li>final field mutation is enabled for the unnamed module.</li>
+     * <li>The field is {@code public} and {@code D} is {@code public} in a package that
+     *     is {@linkplain Module#isExported(String) exported} to all modules.</li>
+     * <li>{@code D} is not a {@linkplain Class#isRecord() record class}.</li>
+     * <li>{@code D} is not a {@linkplain Class#isHidden() hidden class}.</li>
+     * <li>The field is non-static.</li>
```

`java.lang.Module.addExports` is updated to add:
```
+     * <p> Exporting a package with this method does not allow the given module to
+     * {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
+     * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
+     * handle with write access} to a public final field declared in a public class
+     * in the package.
```

`java.lang.Module.addOpens` is updated to add:
```
-     * If this module has <em>opened</em> a package to at least the caller
-     * module then update this module to open the package to the given module.
-     * Opening a package with this method allows all types in the package,
+     * If this module has <em>opened</em> the given package to at least the caller
+     * module, then update this module to also open the package to the given module.
+     *
+     * <p> Opening a package with this method allows all types in the package,
      * and all their members, not just public types and their public members,
-     * to be reflected on by the given module when using APIs that support
-     * private access or a way to bypass or suppress default Java language
+     * to be reflected on by the given module when using APIs that either support
+     * private access or provide a way to bypass or suppress Java language
      * access control checks.
      *
+     * <p> Opening a package with this method does not allow the given module to
+     * {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
+     * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
+     * handle with write access} to a final field declared in a class in the package.
```

`java.lang.ModuleLayer.Controller.addExports` is updated to add:
```
+         * <p> Exporting a package with this method does not allow the target module to
+         * {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
+         * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
+         * handle with write access} to a public final field declared in a public class
+         * in the package.
```

`java.lang.ModuleLayer.Controller.addOpens` is updated to add:
```
+         * <p> Opening a package with this method does not allow the target module
+         * to {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain
+         * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method
+         * handle with write access} to a final field declared in a class in the package.
```

java/lang/reflect/doc-files/MutationMethods.html is added (attached as the html doesn't inline).

`java.lang.invoke.MethodHandle.Lookup.unreflectSetter(Field)` delegates the conditions for when write access is allowed to `Field.set`. Consequently, no changes are proposed to API docs for `unreflectSetter`.

The command line options `--enable-final-field-mutation=module[,module..]` and `--illegal-reflective-final-mutation=(allow,warn,deny)` are added. The default value for the latter will be "warn".  The man page for the "java" launcher will be updated to document these command line options, the following is the current draft of the changes to man page:
```
+`--enable-final-field-mutation` *module*\[,*module*...\]
+:   Mutation of final fields is possible with the reflection API of the Java Platform.
+    _However, it compromises safety and performance in all programs.
+    This option allows code_ in the specified modules to mutate final fields by reflection.
+    Attempts by code in any other module to mutate final fields by reflection are deemed _illegal_.
+
+    *module* can be the name of a module on the module path, or `ALL-UNNAMED` to indicate
+    code on the class path.
+
+-`--illegal-final-field-mutation=`*parameter*
+:   This option specifies a mode for how illegal final field mutation is handled:
+
+    > **Note:** This option will be removed in a future release.
+
+    -   `allow`: This mode allows illegal final field mutation in all modules,
+        without any warings.
+
+    -   `warn`: This mode is identical to `allow` except that a warning message is
+        issued for the first illegal final field mutation performaed in a module.
+        This mode is the default for the current JDK but will change in a future
+        release.
+
+    -   `debug`: This mode is identical to `allow` except that a warning message
+        and stack trace are printed for every illegal final field mutation.
+
+    -   `deny`: This mode disables final field mutation. That is, any illegal final
+        field mutation access causes an `IllegalAccessException`. This mode will
+        become the default in a future release.
+
+    To verify that your application is ready for a future version of the JDK,
+    run it with `--illegal-final-field-mutation=deny` along with any necessary
+    `--enable-final-field-mutation` options.
```

The "Main Manifest" section of the JAR File Specification will be updated to document the JDK-specific JAR file attribute "Enable-Reflective-Final-Mutation" allowed in executable JAR files:
```
+-   Enable-Final-Field-Mutation: Enables final mutation for all code on the
+    class path (including the code in the executable JAR itself). The only
+    supported value is `ALL-UNNAMED`; no other module name can be given. It
+    is equivalent to running with `--enable-final-field-mutation ALL-UNNAMED`.
```

The JNI spec will be updated to add the following note to JNI `Set<type>Field`:
```
- calling `GetFieldID()`.
+ calling `GetFieldID()`. Specifying the field ID of a final instance field is
+ permitted but may result in an unexpected exception or a fatal crash.
```
and  `SetStatic<type>Field` is similarly updated to add:
```
- `GetStaticFieldID()`.
+ `GetStaticFieldID()`. Specifying the field ID of a final static field  is
+ permitted but may result in an unexpected exception or a fatal crash." 
```

The JDK-specific command line option `-Xcheck:jni` is updated to print a warning when attempting to mutate a final field with `Set<type>Field` or `SetStatic<type>Field`. The description of the `-Xcheck:jni` option in the "java" man page is updated to add the following to the cases that result in a warning:
```
+    - A JNI call was made to mutate a final field.
```

Additionally, if the application is run with `-Xlog:jni=debug`, then mutation of final fields (both instance and static) is logged. 

Warning printed by `Field.set` or `Lookup.unreflectSetter`.
-------

At most one warning is printed for any particular module, and only if a warning has not yet been issued for that module. The warning is written to the standard error stream. The following is an example warning when code using `Field.set` on the class path mutates a final field of another class on the class path.
```
WARNING: Final field value in class CommandLineTestHelper$1C has been mutated reflectively by class CommandLineTestHelper in unnamed module @14dad5dc (file:/dir/CommandLineTest.d/)
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
```

Identifying final field mutation
-------

Two mechanisms allow to identify whether and where a program mutates final fields.

- The `--illegal-final-field-mutation=debug`, described above.

- JFR will emit a new event, `jdk.FinalFieldMutation` (enabled by default), whenever code mutates a final instance field or uses `Lookup.unreflectSetter` to get a `MethodHandle` with write access to a reflected final field. The event identifies the class declaring the final field, the name of the final field, and the stack trace to show where the final field mutation is coming from.


Comments
Moving updated request to Approved; thanks for the explanation [~alanb].
18-11-2025

[~darcy] We've had to withdraw and re-finalize the CSR with a small change that came up during the review (thanks Chen for asking a good question that forced us to re-examine this part of the spec). Field.set lists 6 conditions that must hold in order to mutate a final instance field. The 3rd condition required that the field's declaring class be in a package that was opened to the caller. This isn't wrong but it is doesn't align with the checks specified by setAccessible(true) when the field is public and the declaring class is public in a package exported to the caller. We have relaxed the check to align with setAccessible(true). If the field is public and the declaring class is public in a package exported to the caller then it is sufficient to meet this condition. For other cases, the package must be open to the caller as before.
16-11-2025

Note: Obtainable VarHandle instances already do not support modification for non-final fields.
14-11-2025

Thanks [~alanb] for the out-of-JBS discussion about this feature; moving to Approved. Typo: nit "dissabled" -> "disabled".
27-10-2025

> Small typo in JNI spec update Thanks, changes from "write accesses" to "write access". (this is the proposed text to add to the Java Platform Specification that is submitted to the JCP rather than the JNI spec.)
17-10-2025

Moving to Provisional, not Approved. Small typo in JNI spec update: ".. other precondition for write accesss did not..." => ".. other precondition for write access did not..." => Please also update "JDK NN" to the specific value; I'll take another pass over the CSR when it is re-Finalized.
17-10-2025

I've updated the CSR specification to match the text in the PR. Minor wording changes only - there are no significant changes since the CSR was initially proposed. I've also attached the output from the apidiff tool.
03-10-2025

[~abuckley] that would be a better phrasing to me. Thanks. [~rpressler] I totally agree that it would be helpful to tell readers there is a problem, I just don't think simply saying it is "undefined" is really that helpful.
28-09-2025

[~dholmes] Assuming the JNI Spec is to be clarified, your recommendation is to move away from "undefined behavior" and towards "unexpected behavior". I found a use of "unexpected" in the JNI Spec (the second paragraph of ch,4) so I wonder if you're OK with: "Using the SetField or SetStaticField function to set the value of a final field is permitted but may result in an unexpected exception or a fatal crash." ?
26-09-2025

[~dholmes] Currently (i.e. before this proposed change), mutating final fields through JNI is already UB, it's just that the spec is silent on the matter. As this JEP doesn't change that, we could leave things as they are, although as a matter of courtesy, I think it's helpful to mention to readers of the spec that mutating finals through JNI doesn't work (but could appear to work, depending on what optimisations the compiler chooses to apply). Otherwise, the spec may give the impression that it does work. For deterministic behaviour, the `-Xcheck:jni` flag will cause a fatal error when attempting to mutate a final. Changing the default behaviour (i.e. without `-Xcheck:jni`) to be deterministic is too complicated without incurring a significant performance hit. Again, today the behaviour is already non-deterministic, and we're not changing that.
26-09-2025

> The JNI spec will be updated so that JNI Set<type>Field or SetStatic<type>Field functions specify that their behavior is undefined when setting the value of final fields. I find this rather unsatisfactory - we don't specify "undefined behaviour" (as such) anywhere else in the JNI specification. It should either be an error or it should be allowed with a warning that it may lead to unexpected behaviour. Just saying "undefined" is too open-ended and makes it sound (to me) like we don't know what we want it to do. As this JEP is only the "prepare to make final mean final" phase perhaps we do not need to update the JNI spec at all for this? (The Xcheck:jni changes are not part of the JNI spec but are in implementation based diagnostic tool documented in the Java command reference.)
26-09-2025

Added JFR event.
17-09-2025

[~darcy] The proposal is unchanged from when the CSR was moved to provisional. The CSR will be updated to list the JFR event, and maybe some other polishing. As always, something might come up during the code review, we'll have to see. Right now, there isn't anything that will require significant re-review in the CSR.
17-09-2025

[~rpressler], I see the corresponding JEP has been moved to Candidate (https://mail.openjdk.org/pipermail/jdk-dev/2025-September/010485.html). Note if there are been substantive changes to what is planned for the JEP, this CSR should be Withdrawn and re-Proposed to restart its CSR review. In particular, the Provisional status of this CSR (which would be sufficient for the JEP to advance to PTT) does not hold if the JEP has had substantive changes since its CSR review. HTH
17-09-2025

Moving to Provisional, not Approved.
15-05-2025

> Java SE NN and later The JEP is not yet a Candidate yet so it's not really possible to commit to a release at this time. In JEPs and JBS issues then we regularly use NN as a placeholder until the target release is locked in. As regards "have it been considered" then the Alternatives section of the JEPs lists the options that have been explored.
06-05-2025

Typos: > specified in a new new section in "5. Features" > fields {@link #getDeclaringClass() declaring class} Should be linkplain > without any warings For this part: > In Java SE NN and later, when a mutation method is invoked, one of the preconditions for providing its caller with write access is that _final field mutation_ is enabled for the caller. Is NN intended to be the release the JEP targets, or a future release? Also, have it been considered to make the command line option indicate the modules and packages that may be modified, instead of the modules and packages that modify others? The previous set can be derived from the latter set, and allows implementations to more aggressively determine if a particular module or package completely prohibits final field modifications.
06-05-2025

- Added `debug` option to help identify final field mutations. - Improved Javadoc
28-04-2025

> What about JFR events? Other kinds of logging? The JEP does not propose a JFR event at this time. Ongoing work to add JFR method tracing may avoid introducing some of the JFR events that have been requested over the years. So I think TBD if an event would be useful to add. For logging, the JEP was recently updated to expand the set of possible values to `--illegal-final-field-mutation` to include "debug". This logs a warning message and stack trace when illegal final field mutation is performance. Additionally, -Xlog:jni+debug logs when JNI does mutates final fields.
27-04-2025

[~alanb], I'd find it helpful if revisions of this CSR stated something to the effect of "out of the N API to write final field, these k APIs don't need to be be updated because ..." Also, I look forward to seeing the MutationMethods discussion when the CSR is re-Proposed.
23-04-2025

I added a paragraph in the Solution to clarify that the JNI spec and the Instrumentation API spec do not contemplate any new behavior w.r.t. `final` field mutation. As for the sun.misc.Unsafe methods that can mutate final fields, they are unsupported and thus out of scope for this CSR (and they will in any case be removed in a future JDK release).
23-04-2025

> IMO, much of the information proposed as updates in Java Platform Specification separate from the API javadoc should be present in the API docs in some form. This would allow the information to be easily linked to from the relevant areas and vastly increase the discoverability of the information to users. In the draft (not reviewed) changes, java/lang/reflect/doc-files/MutationMethods.html is the page and is linked to from Field.set and Lookup.unreflectSetter. It's also indexed so a javadoc search for "Mutation methods" will find it. > I was expected to see some explicit discussion of agents here. This JEP doesn't impact agents; there are no changes to the JVMTI or java.lang.instrument APIs/specification. It's possible of course that there are agents that use Field.set, JNI or Unsafe to mutate finals but such usages are no different to that of a library or application doing the same. So nothing to call out in the CSR at this time
23-04-2025

Moving back to Draft. IMO, much of the information proposed as updates in Java Platform Specification separate from the API javadoc should be present in the API docs in some form. This would allow the information to be easily linked to from the relevant areas and vastly increase the discoverability of the information to users. Candidate locations for such information would include the java.lang.reflect package-level docs or a new doc in java.lang, as added for restricted methods: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/doc-files/RestrictedMethods.html#restricted
22-04-2025