Skip to content

[Java] support copy non-serializable objects such as java.lang.Package #2941

Open

Description

@thmd

Search before asking

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

Version

I'm facing an issue copying Hibernate objects using fory.copy(object) whereas fory.deserialize(fory.serialize(object)) works fine for the same object.

java.lang.RuntimeException: Create sequential serializer failed, class: class org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader java.lang.UnsupportedOperationException: Class class java.lang.Package doesn't support serialization. at org.apache.fory.resolver.ClassResolver.getSerializerClass(ClassResolver.java:920) at org.apache.fory.resolver.ClassResolver.getSerializerClass(ClassResolver.java:854) at org.apache.fory.builder.BaseObjectCodecBuilder.lambda$getOrCreateSerializer$8(BaseObjectCodecBuilder.java:607) at org.apache.fory.builder.BaseObjectCodecBuilder.lambda$typeResolver$2(BaseObjectCodecBuilder.java:247) at org.apache.fory.builder.JITContext.asyncVisitFory(JITContext.java:159) at org.apache.fory.builder.BaseObjectCodecBuilder.typeResolver(BaseObjectCodecBuilder.java:247) at org.apache.fory.builder.BaseObjectCodecBuilder.typeResolver(BaseObjectCodecBuilder.java:243) at org.apache.fory.builder.BaseObjectCodecBuilder.getOrCreateSerializer(BaseObjectCodecBuilder.java:607) at org.apache.fory.builder.BaseObjectCodecBuilder.readOrGetSerializerForDeclaredType(BaseObjectCodecBuilder.java:2164) at org.apache.fory.builder.BaseObjectCodecBuilder.readChunk(BaseObjectCodecBuilder.java:2056) at org.apache.fory.builder.BaseObjectCodecBuilder.lambda$deserializeForMap$47e820f7$1(BaseObjectCodecBuilder.java:1995) at org.apache.fory.codegen.Expression$While.<init>(Expression.java:2209) at org.apache.fory.builder.BaseObjectCodecBuilder.deserializeForMap(BaseObjectCodecBuilder.java:1967) at org.apache.fory.builder.BaseObjectCodecBuilder.deserializeForNotNull(BaseObjectCodecBuilder.java:1662) at org.apache.fory.builder.BaseObjectCodecBuilder.deserializeForNotNull(BaseObjectCodecBuilder.java:1621) at org.apache.fory.builder.BaseObjectCodecBuilder.lambda$deserializeForNullable$26(BaseObjectCodecBuilder.java:1552) at org.apache.fory.builder.BaseObjectCodecBuilder.readRef(BaseObjectCodecBuilder.java:1572) at org.apache.fory.builder.BaseObjectCodecBuilder.deserializeForNullable(BaseObjectCodecBuilder.java:1552) at org.apache.fory.builder.ObjectCodecBuilder.lambda$deserializeGroup$135afeca$1(ObjectCodecBuilder.java:560) at org.apache.fory.codegen.ExpressionOptimizer.invokeGenerated(ExpressionOptimizer.java:49) at org.apache.fory.builder.ObjectCodecOptimizer.invokeGenerated(ObjectCodecOptimizer.java:138) at org.apache.fory.builder.ObjectCodecBuilder.deserializeGroup(ObjectCodecBuilder.java:577) at org.apache.fory.builder.ObjectCodecBuilder.buildDecodeExpression(ObjectCodecBuilder.java:465) at org.apache.fory.builder.BaseObjectCodecBuilder.genCode(BaseObjectCodecBuilder.java:264) at org.apache.fory.codegen.CompileUnit.getCode(CompileUnit.java:53) at org.apache.fory.codegen.JaninoUtils.toBytecode(JaninoUtils.java:81) at org.apache.fory.codegen.JaninoUtils.toBytecode(JaninoUtils.java:73) at org.apache.fory.codegen.CodeGenerator.compile(CodeGenerator.java:145) at org.apache.fory.builder.CodecUtils.loadOrGenCodecClass(CodecUtils.java:113) at org.apache.fory.builder.CodecUtils.lambda$loadOrGenObjectCodecClass$0(CodecUtils.java:53) at org.apache.fory.builder.CodecUtils.loadSerializer(CodecUtils.java:164) ... 172 common frames omitted Wrapped by: java.lang.RuntimeException: java.lang.UnsupportedOperationException: Class class java.lang.Package doesn't support serialization. at org.apache.fory.builder.CodecUtils.loadSerializer(CodecUtils.java:170) at org.apache.fory.builder.CodecUtils.loadOrGenObjectCodecClass(CodecUtils.java:50) at org.apache.fory.serializer.CodegenSerializer.loadCodegenSerializer(CodegenSerializer.java:49) ... 170 common frames omitted Wrapped by: java.lang.RuntimeException: Create sequential serializer failed, class: class org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader at org.apache.fory.serializer.CodegenSerializer.loadCodegenSerializer(CodegenSerializer.java:52) at org.apache.fory.resolver.ClassResolver.lambda$getObjectSerializerClass$3(ClassResolver.java:1024) at org.apache.fory.builder.JITContext.registerSerializerJITCallback(JITContext.java:121) at org.apache.fory.resolver.ClassResolver.getObjectSerializerClass(ClassResolver.java:1022) at org.apache.fory.resolver.ClassResolver.getSerializerClass(ClassResolver.java:976) at org.apache.fory.resolver.ClassResolver.getSerializerClass(ClassResolver.java:854) at org.apache.fory.resolver.ClassResolver.createSerializer(ClassResolver.java:1260) at org.apache.fory.resolver.ClassResolver.getOrUpdateClassInfo(ClassResolver.java:1182) at org.apache.fory.Fory.copyObject(Fory.java:1393) at org.apache.fory.serializer.AbstractObjectSerializer.copyFields(AbstractObjectSerializer.java:788) at org.apache.fory.serializer.AbstractObjectSerializer.copy(AbstractObjectSerializer.java:702) at org.apache.fory.Fory.copyObject(Fory.java:1488) at org.apache.fory.Fory.copyObject(Fory.java:1448) at org.apache.fory.serializer.AbstractObjectSerializer.copyFields(AbstractObjectSerializer.java:788) at org.apache.fory.serializer.AbstractObjectSerializer.copy(AbstractObjectSerializer.java:702) at org.apache.fory.Fory.copyObject(Fory.java:1488) at org.apache.fory.Fory.copyObject(Fory.java:1448) at org.apache.fory.serializer.AbstractObjectSerializer.copyFields(AbstractObjectSerializer.java:788) at org.apache.fory.serializer.AbstractObjectSerializer.copy(AbstractObjectSerializer.java:702) at org.apache.fory.Fory.copyObject(Fory.java:1488) at org.apache.fory.Fory.copyObject(Fory.java:1448) at org.apache.fory.serializer.AbstractObjectSerializer.copyFields(AbstractObjectSerializer.java:788) at org.apache.fory.serializer.AbstractObjectSerializer.copy(AbstractObjectSerializer.java:702) at org.apache.fory.Fory.copyObject(Fory.java:1488) at org.apache.fory.serializer.collection.CollectionSerializers$DefaultJavaCollectionSerializer.copy(CollectionSerializers.java:734) at org.apache.fory.Fory.copyObject(Fory.java:1488) at org.apache.fory.Fory.copyObject(Fory.java:1448) at org.apache.fory.serializer.AbstractObjectSerializer.copyFields(AbstractObjectSerializer.java:788) at org.apache.fory.serializer.AbstractObjectSerializer.copy(AbstractObjectSerializer.java:702) at org.apache.fory.Fory.copyObject(Fory.java:1488) at org.apache.fory.Fory.copyObject(Fory.java:1448) at org.apache.fory.serializer.AbstractObjectSerializer.copyFields(AbstractObjectSerializer.java:788) at org.apache.fory.serializer.AbstractObjectSerializer.copy(AbstractObjectSerializer.java:702) at org.apache.fory.Fory.copyObject(Fory.java:1488) at org.apache.fory.Fory.copyObject(Fory.java:1448) at org.apache.fory.Fory.copy(Fory.java:1372) ... 135 common frames omitted Wrapped by: org.apache.fory.exception.CopyException: java.lang.RuntimeException: Create sequential serializer failed, class: class org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader at org.apache.fory.Fory.processCopyError(Fory.java:373)

Component(s)

Java

Minimal reproduce step

Trying to just copy the object fails.

What did you expect to see?

A successful deepCopy of the object.

What did you see instead?

The given exception.

Anything Else?

No response

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Activity

  1. added
    bugSomething isn't working
    on Nov 27, 2025
  2. chaokunyang commented on Nov 27, 2025

    @chaokunyang
    Collaborator

    @thmd could you provide a reproduction example?

  3. chaokunyang commented on Nov 27, 2025

    @chaokunyang
    Collaborator

    It seems you are copy a java.lang.Package object, which is not serializable. I think we can just skip copy for such objects instead of raise errors

  4. changed the title [-]Fory copy fails while deepCopy works[/-] [+]Fory copy fails for non-serializable java.lang.Package object[/+] on Nov 27, 2025
  5. thmd commented on Nov 27, 2025

    @thmd
    Author

    I think this reproduces the issue.
    fory-hibernate-test.zip

  6. thmd commented on Nov 27, 2025

    @thmd
    Author
  7. chaokunyang commented on Dec 8, 2025

    @chaokunyang
    Collaborator

    I have no plans to introduce another type dispatch mechnisim for copy, it will use same Serializer dispatch system

    We could defer the exception to runtime. Instead of raise exception when get serializer, we could return a Serializer for any types. But for types which don't support serialization, we throw exception real serialization happens.

  8. chaokunyang commented on Dec 8, 2025

    @chaokunyang
    Collaborator

    For any unserializable objects, the copy should just return original obejct

  9. changed the title [-]Fory copy fails for non-serializable java.lang.Package object[/-] [+][Java] support copy non-serializable objects such as java.lang.Package[/+] on Dec 8, 2025
  10. thmd commented on Dec 14, 2025

    @thmd
    Author

    I have no plans to introduce another type dispatch mechnisim for copy, it will use same Serializer dispatch system

    We could defer the exception to runtime. Instead of raise exception when get serializer, we could return a Serializer for any types. But for types which don't support serialization, we throw exception real serialization happens.

    I don't understand about Fory enough to tell what is meant by dispatch here. My big question if they are using similar/same code why is serialize succeed when copy fails? Does that have anything to do with transient fields? Does copy copies transient fields?

    I think I can more meaningfully comment based on my use cases if I understand the difference between copy and serializae and why one succeed and one don't.

  11. jim-parsons commented on Dec 14, 2025

    @jim-parsons
    Contributor

    I have no plans to introduce another type dispatch mechnisim for copy, it will use same Serializer dispatch system
    We could defer the exception to runtime. Instead of raise exception when get serializer, we could return a Serializer for any types. But for types which don't support serialization, we throw exception real serialization happens.

    I don't understand about Fory enough to tell what is meant by dispatch here. My big question if they are using similar/same code why is serialize succeed when copy fails? Does that have anything to do with transient fields? Does copy copies transient fields?

    I think I can more meaningfully comment based on my use cases if I understand the difference between copy and serializae and why one succeed and one don't.

    After some debug work, here is what i've found;

    The serialize/deserialize function will ignore the transient field, but copy won't , because the copy function need to calculate all the field value , which currently depended on fory#serialize function.

    So in this case, when copy filed protected transient FastSessionServices fastSessionServices; it failed, because it has unsupport serialize filed ClassLoader

    Image

    The serialize class wont handle transient field

    Image
  12. chaokunyang commented on Dec 15, 2025

    @chaokunyang
    Collaborator

    @jim-parsons @thmd How about checking whether transient fields are serializable in constructor of AbstractObjectSerializer, if not, we just copy transient fields by ref directly

  13. thmd commented on Dec 15, 2025

    @thmd
    Author

    I feel that copy original ref when it's not mutable breaks the contract of DeepCopy which is what this is useful for. I almost want two flags for copy if possible.

    copyTransient - true/false
    copyOriginalRefIfNeeded - true/false
    
  14. chaokunyang commented on Dec 15, 2025

    @chaokunyang
    Collaborator

    Transient fields always needs copy, some class use jdk writeObject/readObject to setup object invariant, Then set transient fields to correct values in those methods. If we don't copy transient fields, the copies object will be in wrong state.

  15. chaokunyang commented on Dec 15, 2025

    @chaokunyang
    Collaborator

    @thmd Or maybe we can just refine the error message, and let users to register a CopySerializer for unsupported classes directly. This is much easier. By default, CopySerializer just return original ref. And user can subclass it to customize the copy logic

  16. thmd commented on Dec 15, 2025

    @thmd
    Author

    There are valid cases for both wanting to DeepCopy an Object with transient fields and without. A DeepCopy with transient field is obviously most complete state copy. But the truth is often (to my knowledge) transient is used to mark we don't care about like here Hibernate framework harness which is often not useful in a DeepCopy use case.

    DeepCopy objets are often used longterm e.g. in a cache which often makes transient references to ORM, Thread etc. invalid. Also most often DeepCopy is basically done using a serialize+deserialize pair which is what I was using before finding Fory.copy and such DeepCopy doesn't copy transient.

    Now there are other cases people do want transient fields where they need a library with Copy (which is rare) or do something manually. So I think it's worth having a Copy without transient option as it allow someone to safely migrate from a serialize+deserialize to Fory.copy for the added performance (both cpu + memory overhead) gains.

    I actually find returning original ref much more risky than skipping transient as DeepCopy is meant to give you an independent copy which original ref copy breaks and does so very silently.

  17. chaokunyang commented on Dec 15, 2025

    @chaokunyang
    Collaborator

    If we enforce users to register a Serializer for unsupported classes directly, users will be aware of t. So there is no risk.

    You can take HashMap as an example, it has size, modCount, table fields. They all are transient.

    If we skip transient fields, we are copying nothing and will get NPE instead.

  18. thmd commented on Dec 15, 2025

    @thmd
    Author

    I actually wasn't aware that HashMap has such fields as transient. It seems like I can't use Fory.copy as a drop-in replacement for a DeepCopy need currently served by a serialize+deserialize and my use cases is better served by that. It would be less efficient than Fory.copy but still better than JDK serialization.

    HashMap could have made them regular (non-transient) field but still skip them in it's custom writeObject, which seems like a better design to me. Anyway it seems I know even lesser than what I thought about java serialization.

  19. sjahedhussini commented on Jun 5, 2026

    @sjahedhussini

    Hi, I'm looking to make my first open source contribution, and this issue caught my attention. I'd like to take a stab at fixing it. From reading the stack trace and the discussion, it looks like the copy path is routing through the same serializer-lookup logic as serialize/deserialize, which hard-fails on non-serializable types like java.lang.Package. I'm thinking the fix lives somewhere in ClassResolver or AbstractObjectSerializer — would love any pointers from the maintainers on the preferred approach before I start. Happy to submit a PR.

  20. sjahedhussini commented on Jun 20, 2026

    @sjahedhussini

    Hello @chaokunyang! After reproducing the error, I traced the error to the method ClassResolver.getSerializerClass(Class<?>, boolean) and

    if (config.checkJdkClassSerializable()) {
      if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))) {
        throw new UnsupportedOperationException(
            String.format("Class %s doesn't support serialization.", cls));
      }
    }
    

    The following code/test produces it:

    Fory fory = Fory.builder()
        .withLanguage(Language.JAVA)
        .requireClassRegistration(false)
        .build();
    fory.copy(String.class.getPackage()); // throws
    

    In order to solve this, my thinking is to let copy bypass this guard without weakening serialize. As in, route these classes to a serializer whose write/read still throw but whose copy works, similar to how ForyCopyableSerializer already gives copy distinct behavior. Before I implement, I wanted to check the design direction, since I saw the earlier discussion about transient fields vs. returning the original reference:
    For opaque JVM-managed classes like java.lang.Package, would you prefer copy() attempt a field-based copy, or return the original reference? And does this bypass guard for copy approach sound right to you, or would you rather the fix be implemented somewhere else? Thank you, I just want to head in the right direction before opening a PR.

  21. itsCodeTide commented on Oct 14, 2026

    @itsCodeTide

    I’ve prepared an alternative fix in #4179 . It makes copy() return non-serializable JDK objects such as java.lang.Package by reference, while keeping serialization of those objects unsupported. This avoids trying to copy JVM-managed internals.
    The change adds direct and nested copy coverage, plus a check that serialization still fails. I also saw that PR #3797 is open for this issue; this PR takes a different approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions