Skip to content

[java] UnsupportedOperationException: can't get field offset on a hidden class in async JIT when serializing class containing List<T> where T is a user-defined type (regression from 1.1.0) #3771

@tex1988

Description

@tex1988

Search before asking

  • I had searched in the issues and found no similar issues.

Version

Component Version
OS Windows 11 Pro 10.0.26200 64-bit
JDK OpenJDK 25.0.2 (build 25.0.2+10-69, 64-bit Server VM)
fory-core (broken) 1.2.0
fory-core (working) 1.1.0

Component(s)

Java

Minimal reproduce step

import org.apache.fory.Fory;
import org.apache.fory.ThreadSafeFory;
import org.apache.fory.config.CompatibleMode;
import org.apache.fory.config.Language;

// Minimal model
static class Item {
    String value;
    int count;
    Item() {}
    Item(String value, int count) { this.value = value; this.count = count; }
}

static class Container {
    List<Item> items;
    Container() {}
    Container(List<Item> items) { this.items = items; }
}

public static void main(String[] args) throws Exception {
    ThreadSafeFory fory = Fory.builder()
            .withLanguage(Language.JAVA)
            .withCompatibleMode(CompatibleMode.COMPATIBLE)
            .withAsyncCompilation(true)
            .requireClassRegistration(true)
            .buildThreadSafeForyPool(4);

    fory.register(Container.class);
    fory.register(Item.class);

    Container obj = new Container(List.of(new Item("x", 1), new Item("y", 2)));
    byte[] bytes = fory.serialize(obj);
    fory.deserialize(bytes);

    Thread.sleep(3000); // wait for async JIT to fire
}

What did you expect to see?

No errors. Async JIT compiles the serializer for Container and injects it silently.

What did you see instead?

java.lang.UnsupportedOperationException: can't get field offset on a hidden class:
  org.apache.fory.serializer.Serializer
  ContainerForyCodec_0/0x000000009d145c00.serializer
    at jdk.unsupported/sun.misc.Unsafe.objectFieldOffset(Unsafe.java:900)
    at org.apache.fory.reflect.InstanceFieldAccessors$InstanceAccessor.fieldOffset(InstanceFieldAccessors.java:136)
    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)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(...)

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)

Triggered on first serialization of any class containing List where T is a user-defined type. Does not trigger for flat classes or List. Does not occur on Fory 1.1.0.

Anything Else?

Root cause analysis:
PR #3702 (feat(java): remove sun.misc.Unsafe for jdk25) changed how Fory defines JIT-generated serializer classes. On JDK 25 a VarHandle-based MR-JAR path is used correctly. On JDK 15–24, the generated serializer class is now defined as a hidden class (via Lookup.defineHiddenClass()), but the callback in onNotifyResult still injects the serializer field via Unsafe.objectFieldOffset() — which the JVM has explicitly forbidden on hidden classes since JEP 371 (JDK 15). This code path was not covered by the MR-JAR fix.

Workaround: withAsyncCompilation(false)

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions