Search before asking
Version
fory-core 1.2.0; JDK 25 / 26 (reproduced on Zulu 26.28+63); withCompatible(true), requireClassRegistration(false), withAsyncCompilation(true), withCodegen(true) (default); Fory and the domain types share one classpath/classloader.
Component(s)
Java
Minimal reproduce step
a record Meta(Link link, Tag tag, Set<Flag> flags, Set<Mode> modes, String label) of nested records, registered on a buildThreadSafeForyPool with the config above, serialize(...) then Thread.sleep to let the compiler threads surface the exception. (I have a ~50-line standalone ForyRepro.java I can attach.)
What did you expect to see?
Serializing a registered composite type (a record with nested registered records) JIT-compiles its codec in the background, fully installs it (nested serializers injected into the codec's serializer/serializer1 fields) and produces no exceptions. The fory-jit-compiler-* threads stay alive and the generated codec is used on subsequent calls. I.e. identical behavior to JDK ≤ 24.
What did you see instead?
serialize(...) returns correct bytes (interpreter fallback), but the background compiler threads throw:
Exception in thread "fory-jit-compiler-2" java.lang.UnsupportedOperationException:
can't get field offset on a hidden class:
org.apache.fory.serializer.Serializer <pkg>.<Type>ForyCodec_0/0x….serializer
at sun.misc.Unsafe.objectFieldOffset(...)
at org.apache.fory.reflect.InstanceFieldAccessors$InstanceAccessor.fieldOffset(InstanceFieldAccessors.java:136)
at org.apache.fory.reflect.InstanceFieldAccessors.createAccessor(InstanceFieldAccessors.java:68)
at org.apache.fory.reflect.ReflectionUtils.setObjectFieldValue(ReflectionUtils.java:471)
at org.apache.fory.builder.Generated$GeneratedSerializer$1.onNotifyResult(Generated.java:85)
at org.apache.fory.builder.JITContext.lambda$registerSerializerJITCallback$0(JITContext.java:96)
(repeated for .serializer1), immediately followed by:
java.lang.NullPointerException: Cannot invoke "java.util.List.iterator()" because the return value of
"java.util.Map.get(Object)" is null
at org.apache.fory.builder.JITContext.lambda$registerSerializerJITCallback$0(JITContext.java:95)
The fory-jit-compiler-* threads die, and the generated codec is left with its nested-serializer fields never injected. Only composite types fail; leaf-type codecs (no nested serializer field) compile cleanly even though they too are hidden classes.
Anything Else?
I tried --add-opens=java.base/java.lang.invoke=ALL-UNNAMED does not help (verified). Fory's JDK25+ docs recommend this flag, so I tested with and without it on Zulu 26.28+63. The exception is identical in both cases. That flag governs MethodHandles.Lookup/defineHiddenClass access; it has no effect on Unsafe.objectFieldOffset, which the JVM rejects for any hidden declaring class regardless of module openness. The failing accessor (InstanceFieldAccessors$InstanceAccessor) never consults a Lookup (tt calls Unsafe.objectFieldOffset directly) so this is a code-path defect, not a missing-access/configuration issue.
On JDK 25+, CodecUtils.codecNeighbor returns the bean class for any type whose loader can see Fory, so CodeGenerator.compileAndLoad defines the codec via DefineClass.defineHiddenNestmate (a hidden class). When withAsyncCompilation(true), nested serializers are injected post-construction through Generated$GeneratedSerializer$1.onNotifyResult → ReflectionUtils.setObjectFieldValue → InstanceFieldAccessors.createAccessor → new InstanceAccessor → Unsafe.objectFieldOffset(field) (InstanceFieldAccessors.java:136), which the JVM rejects for hidden classes. The failing task's finally then runs hasJITResult.clear() while a concurrent successful task is at JITContext.java:95 (for (… : hasJITResult.get(callback.id()))), causing the NPE. Only composite types are affected (leaf codecs have no nested serializer field to inject).
Workaround:
withAsyncCompilation(false) (keeps codegen/JIT) or withCodegen(false) (interpreter only).
Are you willing to submit a PR?
Search before asking
Version
fory-core 1.2.0; JDK 25 / 26 (reproduced on Zulu 26.28+63);
withCompatible(true),requireClassRegistration(false),withAsyncCompilation(true),withCodegen(true)(default); Fory and the domain types share one classpath/classloader.Component(s)
Java
Minimal reproduce step
a record
Meta(Link link, Tag tag, Set<Flag> flags, Set<Mode> modes, String label)of nested records, registered on abuildThreadSafeForyPoolwith the config above,serialize(...)thenThread.sleepto let the compiler threads surface the exception. (I have a ~50-line standalone ForyRepro.java I can attach.)What did you expect to see?
Serializing a registered composite type (a record with nested registered records) JIT-compiles its codec in the background, fully installs it (nested serializers injected into the codec's serializer/serializer1 fields) and produces no exceptions. The fory-jit-compiler-* threads stay alive and the generated codec is used on subsequent calls. I.e. identical behavior to JDK ≤ 24.
What did you see instead?
serialize(...) returns correct bytes (interpreter fallback), but the background compiler threads throw:
The fory-jit-compiler-* threads die, and the generated codec is left with its nested-serializer fields never injected. Only composite types fail; leaf-type codecs (no nested serializer field) compile cleanly even though they too are hidden classes.
Anything Else?
I tried
--add-opens=java.base/java.lang.invoke=ALL-UNNAMEDdoes not help (verified). Fory's JDK25+ docs recommend this flag, so I tested with and without it on Zulu 26.28+63. The exception is identical in both cases. That flag governsMethodHandles.Lookup/defineHiddenClassaccess; it has no effect onUnsafe.objectFieldOffset, which the JVM rejects for any hidden declaring class regardless of module openness. The failing accessor (InstanceFieldAccessors$InstanceAccessor) never consults a Lookup (tt calls Unsafe.objectFieldOffset directly) so this is a code-path defect, not a missing-access/configuration issue.On JDK 25+,
CodecUtils.codecNeighborreturns the bean class for any type whose loader can see Fory, soCodeGenerator.compileAndLoaddefines the codec viaDefineClass.defineHiddenNestmate(a hidden class). WhenwithAsyncCompilation(true), nested serializers are injected post-construction throughGenerated$GeneratedSerializer$1.onNotifyResult→ReflectionUtils.setObjectFieldValue→InstanceFieldAccessors.createAccessor→new InstanceAccessor→Unsafe.objectFieldOffset(field)(InstanceFieldAccessors.java:136), which the JVM rejects for hidden classes. The failing task's finally then runshasJITResult.clear()while a concurrent successful task is atJITContext.java:95(for (… : hasJITResult.get(callback.id()))), causing the NPE. Only composite types are affected (leaf codecs have no nested serializer field to inject).Workaround:
withAsyncCompilation(false)(keeps codegen/JIT) orwithCodegen(false)(interpreter only).Are you willing to submit a PR?