alibaba/spring-ai-alibaba · error · RuntimeException
无法修改final字段
Error message
无法修改final字段: <fieldName>
What it means
NacosModelInjector.modifyFinalFieldWithUnsafe fails to modify a final field of an object through reflection plus an Unsafe internal API, and wraps any failure in a RuntimeException naming the field. This usually means the Unsafe access technique is not viable on the current JVM (method renamed/removed, module access denied) or the target object/field is mismatched.
Solutions
- Read the wrapped cause to see whether it is NoSuchMethodException (JDK removed the API) or IllegalAccessException (module restrictions).
- Run the JVM with --add-opens flags for the relevant modules if encapsulation is the blocker.
- Prefer designing the target class with a mutable field or constructor injection instead of final-field mutation.
- Pin/adjust the JDK version to one supported by NacosModelInjector's Unsafe approach.
Example fix
// before: JVM flags omitted java -jar app.jar // after: open required modules so Unsafe reflection works java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar
Defensive patterns
Strategy: try-catch
Validate before calling
// Detect Unsafe availability on the current JVM before attempting injection
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
unsafeClass.getMethod("staticFieldOffset", Field.class);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new IllegalStateException("Unsafe final-field injection unsupported on this JVM: " + e.getMessage());
} Try / catch
try {
injector.modifyFinalField(target, field, value);
} catch (RuntimeException e) {
logger.error("Final field {} not modifiable: {}", field.getName(), e.getCause(), e);
throw new IllegalStateException("Configuration injection failed; fix the field design instead", e);
} Prevention
- Prefer constructor/setter injection over mutating final fields.
- Run with --add-opens flags if JPMS blocks the reflective access.
- Pin a JDK version known to support the Unsafe internal API used by the injector.
When it happens
Trigger: modifyFinalField called on a field of an object where the Unsafe class lookup, staticFieldOffset invocation, or putObject invocation fails — e.g. on JDK 9+ with strong encapsulation blocking sun.misc.Unsafe internals, or a null targetObject.
Common situations: Upgrading to a newer JDK that removes or restricts the internal Unsafe API (the code reflects on method names like staticFieldOffset); running with a Security Manager or JPMS settings blocking reflective access; injecting into record-like/final classes.
Related errors
- Cannot instantiate array type
- ChatOptions implementation missing copy() method
- Constructor did not produce a Map instance:
- copy() method did not return a ChatOptions instance
- Could not find executeAgent method in AgentToolExecutor…
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/1605b69dcf7c5782.
Report an issue: GitHub.
Appendix: source
Thrown at spring-boot-starters/spring-ai-alibaba-starter-config-nacos/src/main/java/com/alibaba/cloud/ai/agent/nacos/NacosModelInjector.java:107
/**
* 使用Unsafe修改final字段(适用于Java 12+)
*/
private static void modifyFinalFieldWithUnsafe(Field field, Object targetObject, Object newValue) throws Exception {
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeInstanceField = unsafeClass.getDeclaredField("theUnsafe");
unsafeInstanceField.setAccessible(true);
Object unsafeInstance = unsafeInstanceField.get(null);
Method putObjectMethod = unsafeClass.getMethod("putObject", Object.class, long.class, Object.class);
Method staticFieldOffsetMethod = unsafeClass.getMethod("staticFieldOffset", Field.class);
long offset = (Long) staticFieldOffsetMethod.invoke(unsafeInstance, field);
putObjectMethod.invoke(unsafeInstance, targetObject, offset, newValue);
}
catch (Exception e) {
throw new RuntimeException("无法修改final字段: " + field.getName(), e);
}
}
}
View on GitHub (pinned to f82da0b50f)