wakilurislam opened a new issue, #3635: URL: https://github.com/apache/fory/issues/3635
### Search before asking - [x] I had searched in the [issues](https://github.com/apache/fory/issues) and found no similar issues. ### Version 0.18.0-SNAPSHOT (also affects 0.17.x and earlier) Java 8 / Java 21, macOS ### Component(s) Java ### Minimal reproduce step When serializing an unregistered class that has `writeReplace()` returning a different type (e.g., a Hibernate/ByteBuddy proxy), Fory writes the **proxy class name** in the outer type info using `NAMED_EXT`. On a different JVM where that proxy class doesn't exist, deserialization fails with `ClassNotFoundException`. ```java // "Proxy" class — only exists on JVM A public class HibernateProxy extends RealEntity { protected Object writeReplace() { return new RealEntity(name, value); } } // JVM A: serialize Fory fory1 = Fory.builder().requireClassRegistration(false).build(); byte[] bytes = fory1.serialize(new HibernateProxy("test", 42)); // JVM B: deserialize (HibernateProxy class not available) Fory fory2 = Fory.builder().requireClassRegistration(false) .withClassLoader(classLoaderWithoutProxy).build(); Object result = fory2.deserialize(bytes); // ClassNotFoundException! ``` The root cause is in `ClassResolver.buildUnregisteredTypeId()` which returns `Types.NAMED_EXT` for writeReplace classes. This causes `writeTypeInfo()` to write the proxy class name bytes to the stream. The `ReplaceResolveSerializer` correctly handles the substitution *inside* `writeData()`, but the outer type info already contains the proxy class name — and the deserializer tries to load it before the serializer can handle the replacement. Stack trace: ``` java.lang.IllegalStateException: Class HibernateProxy not found from classloaders [...] at org.apache.fory.resolver.TypeResolver.loadClass(TypeResolver.java:1095) at org.apache.fory.resolver.ClassResolver.populateBytesToTypeInfo(ClassResolver.java:1959) at org.apache.fory.resolver.TypeResolver.readTypeInfoFromBytes(TypeResolver.java:754) at org.apache.fory.resolver.TypeResolver.readTypeInfo(TypeResolver.java:546) at org.apache.fory.context.ReadContext.readRef(ReadContext.java:528) Caused by: java.lang.ClassNotFoundException: HibernateProxy ``` ### What did you expect to see? Deserialization should succeed and return a `RealEntity` object (the result of `writeReplace()`), regardless of whether the proxy class exists on the deserializing JVM. The proxy class name should never appear in the serialized bytes since `writeReplace()` substitutes it before the data is written. This is the same pattern Fory already handles correctly for lambdas (`LAMBDA_STUB_ID`) and JDK proxies (`JDK_PROXY_STUB_ID`) — a compact internal type ID is written instead of the class name. ### What did you see instead? `ClassNotFoundException` / `IllegalStateException` during deserialization because `readTypeInfo()` tries to load the proxy class name from the byte stream. The proxy class name is baked into the outer type info by `writeTypeInfo()` before `ReplaceResolveSerializer.write()` gets a chance to handle the substitution. This affects any framework that generates proxy classes with `writeReplace()`, including: - Hibernate (ByteBuddy proxies for lazy-loaded entities) - Spring AOP proxies - Any custom proxy framework using `writeReplace()` for serialization transparency ### Anything Else? **Proposed fix**: In `ClassResolver.buildUnregisteredTypeId()`, return `REPLACE_STUB_ID` instead of `Types.NAMED_EXT` for classes detected as writeReplace candidates. This writes a compact 1-byte internal type ID (no class name), matching the existing lambda/proxy stub pattern. ```diff // ClassResolver.java @Override protected int buildUnregisteredTypeId(Class<?> cls, Serializer<?> serializer) { - if (serializer == null && !cls.isEnum() && useReplaceResolveSerializer(cls)) { - return Types.NAMED_EXT; + if (!cls.isEnum() + && (serializer instanceof ReplaceResolveSerializer || useReplaceResolveSerializer(cls))) { + return REPLACE_STUB_ID; } return super.buildUnregisteredTypeId(cls, serializer); } ``` Also requires guarding `addSerializer()` to not overwrite the pre-registered `typeIdToTypeInfo[REPLACE_STUB_ID]` entry. I have a working fix with tests and will submit a PR. ### Are you willing to submit a PR? - [x] I'm willing to submit a PR! -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
