This used to be easy in Java 8 and I want to know if it's still possible in Java 21. I've seen a bunch of old posts, all giving different methods for this, none of which worked. I'm hoping for a universal solution, but I'll include details of my use case too.
What do I need it for and what can I control?
I am making a Minecraft plugin - that means my code is in a jar
file and is not loaded instantly alongside the server, that happens a few seconds later using a class loader. This being a plugin also means I can't control environment variables or parameters when JVM is started.
I want to change the value of a private final
commandMap
variable in org.bukkit.plugin.SimplePluginManager
class. It's not primitive.
What have I tried?
I cannot use just field.setAccessible(true);
, because modern java throws IllegalArgumentException: Can not set final field
.
I cannot make the field non-final using modifiers
field (code below), since java.lang.reflect.Field
fields have been hidden.
field.setAccessible(true);
Field modifiersField = field.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
I tried org.apache.commons.lang3.reflect.FieldUtils
only to realize it uses the same trick as above.
I tried using sun.misc.Unsafe
from this answer but I don't know how to apply it to a non-static variable. The comments say this works on Java 21.
Yes, you can change the value of a final field on an object using Unsafe. However, the fact that this is now prevented even through the reflection API has a strong reason: the JVM is allowed to use any optimization it sees fit that relies on the assumption that final fields never change. You can read the full detail and reasoning behind this in JEP 8349536.
Here's some code that proves both that, yes, you can change the field, and no, the JVM won't care unless you use the Reflection API to read the field:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class Test {
private final String x = "x";
public static void main(String[] args) throws Exception {
Unsafe unsafe = unsafe();
Test test = new Test();
Field field = Test.class.getDeclaredField("x");
long offset = unsafe.objectFieldOffset(field);
System.out.println("Before: " + test.x);
unsafe.putObject(test, offset, "modified");
System.out.println("After, regular access: " + test.x);
System.out.println("After, using reflection: " + field.get(test));
}
private static Unsafe unsafe() throws Exception {
// Get Unsafe instance
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
}
The output on my machine is:
Before: x
After, regular access: x
After, using reflection: modified
I tried out some variations, like making test
a volatile variable, using volatile test2
and assigning after the change, using a new thread to read the field. Nothing worked.
You can try the above and maybe it works for you, but it will at best be a brittle and error-prone solution.
I will add that the mere fact that, in this particular code, the change is seen through reflection but not direct access, doesn't tell us anything deep about reflection and final fields. It's just an artifact of optimizations existing for direct field access, and access by reflection goes around them. After the optimization known as accessor inflation kicks in, the JVM will have generated bytecode to directly access the field, and then you may see the same issue again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With