From 2fcbab4926cd8abef3f9254da5714b335527ad95 Mon Sep 17 00:00:00 2001 From: sjahed Date: Wed, 17 Jun 2026 20:25:21 -0400 Subject: [PATCH 01/15] Added a test to reproduce the issue 2941. --- .../src/test/java/org/apache/fory/ForyCopyTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index bd4cd7d418..d7bb548f40 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -79,6 +79,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import org.apache.fory.collection.LazyMap; +import org.apache.fory.config.Language; import org.apache.fory.context.CopyContext; import org.apache.fory.context.ReadContext; import org.apache.fory.context.WriteContext; @@ -454,4 +455,15 @@ public void testComplexCollectionCopy() { assertEquals(fory.copy(collectionFields).toCanEqual(), collectionFields.toCanEqual()); } } + + @Test + public void testCopyNonSerializablePackage() { + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withRefCopy(true) + .requireClassRegistration(false) // get past the registration gate + .build(); + Package pkg = String.class.getPackage(); // java.lang + Package copy = fory.copy(pkg); // now expect the real bug + } } From c4f782b603be6c11129acfe4d36d3f5e4859df6e Mon Sep 17 00:00:00 2001 From: sjahed Date: Wed, 24 Jun 2026 18:39:29 -0400 Subject: [PATCH 02/15] added a test to reproduce issue 2941 --- .../src/main/java/org/apache/fory/resolver/ClassResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index fe58520121..2852c4927c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -207,7 +207,7 @@ */ @NotThreadSafe @SuppressWarnings({"rawtypes", "unchecked"}) -public class ClassResolver extends TypeResolver { +public class ClassResolver extends TypeResolver { private static final Logger LOG = LoggerFactory.getLogger(ClassResolver.class); public static final int NATIVE_START_ID = Types.BOUND; From 69cbf245b9aaa1a5dd9b338e43860bfeacca5e03 Mon Sep 17 00:00:00 2001 From: sjahed Date: Thu, 25 Jun 2026 09:25:37 -0400 Subject: [PATCH 03/15] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../apache/fory/resolver/ClassResolver.java | 37 +++--------- .../serializer/NonSerializableSerializer.java | 52 ++++++++++++++++ .../java/org/apache/fory/ForyCopyTest.java | 59 +++++++++++++++---- 3 files changed, 108 insertions(+), 40 deletions(-) create mode 100644 java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index 2852c4927c..b7b1bfb9f5 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -114,36 +114,10 @@ import org.apache.fory.platform.AndroidSupport; import org.apache.fory.platform.GraalvmSupport; import org.apache.fory.reflect.ReflectionUtils; -import org.apache.fory.serializer.ArraySerializers; -import org.apache.fory.serializer.BufferSerializers; +import org.apache.fory.serializer.*; import org.apache.fory.serializer.CodegenSerializer.LazyInitBeanSerializer; -import org.apache.fory.serializer.CompatibleSerializer; -import org.apache.fory.serializer.CopyOnlyObjectSerializer; -import org.apache.fory.serializer.EnumSerializer; -import org.apache.fory.serializer.ExceptionSerializers; -import org.apache.fory.serializer.ExternalizableSerializer; -import org.apache.fory.serializer.ForyCopyableSerializer; -import org.apache.fory.serializer.JavaSerializer; -import org.apache.fory.serializer.JdkProxySerializer; -import org.apache.fory.serializer.LambdaSerializer; -import org.apache.fory.serializer.LocaleSerializer; -import org.apache.fory.serializer.NoneSerializer; -import org.apache.fory.serializer.ObjectSerializer; -import org.apache.fory.serializer.OptionalSerializers; -import org.apache.fory.serializer.PrimitiveArraySerializers; -import org.apache.fory.serializer.PrimitiveSerializers; -import org.apache.fory.serializer.ReplaceResolveSerializer; -import org.apache.fory.serializer.SerializedLambdaSerializer; -import org.apache.fory.serializer.Serializer; -import org.apache.fory.serializer.Serializers; -import org.apache.fory.serializer.Shareable; -import org.apache.fory.serializer.SqlTimeSerializers; -import org.apache.fory.serializer.TimeSerializers; -import org.apache.fory.serializer.UnknownClass; import org.apache.fory.serializer.UnknownClass.UnknownEmptyStruct; import org.apache.fory.serializer.UnknownClass.UnknownStruct; -import org.apache.fory.serializer.UnknownClassSerializers; -import org.apache.fory.serializer.UnsignedSerializers; import org.apache.fory.serializer.collection.ChildContainerSerializers; import org.apache.fory.serializer.collection.CollectionLikeSerializer; import org.apache.fory.serializer.collection.CollectionSerializer; @@ -1498,9 +1472,12 @@ public Class getSerializerClass(Class cls, boolean code } } if (config.checkJdkClassSerializable()) { - if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", cls)); + if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))){ + // Issue #2941: previously threw UnsupportedOperationException here, which also broke + // Fory.copy() for non-serlializble JDK classes (e.g. java.lang.Package). Route to a + // serializer that still refuses binary serialization (write/read throw) but + // supports copy() via reflective field copy + return NonSerializableSerializer.class; } } if (ScalaTypes.SCALA_AVAILABLE && ReflectionUtils.isScalaSingletonObject(cls)) { diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java new file mode 100644 index 0000000000..d8bff538b7 --- /dev/null +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.serializer; + +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.resolver.TypeResolver; + +/** + * Serializer for non-Serializable JDK classes (e.g. {@code java.lang.Package}). + * + *

Binary serialization remains unsupported — {@link #write} and {@link #read} throw + * {@link UnsupportedOperationException}, preserving the prior behavior of {@code ClassResolver}'s + * JDK-serializable guard. However {@code copy} is supported via the reflection-based field copy + * inherited from {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs + * that transitively contain such classes (issue #2941). + */ +public final class NonSerializableSerializer extends AbstractObjectSerializer { + + public NonSerializableSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type); + } + + @Override + public void write(WriteContext writeContext, T value) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type)); + } + + @Override + public T read(ReadContext readContext) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type)); + } +} diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index d7bb548f40..7edafc2ee3 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -84,6 +84,7 @@ import org.apache.fory.context.ReadContext; import org.apache.fory.context.WriteContext; import org.apache.fory.exception.ForyException; +import org.apache.fory.exception.SerializationException; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.EnumSerializerTest; import org.apache.fory.serializer.EnumSerializerTest.EnumFoo; @@ -456,14 +457,52 @@ public void testComplexCollectionCopy() { } } - @Test - public void testCopyNonSerializablePackage() { - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .withRefCopy(true) - .requireClassRegistration(false) // get past the registration gate - .build(); - Package pkg = String.class.getPackage(); // java.lang - Package copy = fory.copy(pkg); // now expect the real bug - } + @Test + public void testCopyNonSerializableJdkClass(){ + // Issue #2941: copying an object that is (or contains) a non-serializable JDK class + // like java.lang.Package used to throw. It should now succeed. + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + Package pkg = String.class.getPackage(); + Package copy = fory.copy(pkg); + Assert.assertNotNull(copy); + } + + @Test + public void testCopyHashMapStillWorks(){ + // Regression for chaokunyang's concern (comments on issue #2941): HashMap keeps + //essential state (size, table) in transient field. The copy path copies all fields + // including transient, so this must still produce an equal map. + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + Map map = new HashMap<>(); + map.put("a", 1); + map.put("b", 2); + Map copy = fory.copy(map); + Assert.assertEquals(map, copy); + } + + @Test + public void testSerializeNonSerializableJdkClassStillThrows(){ + // Regression guard: we must not have weakened serialization. Serliazing a + // non-serliazable JDK class must still throw, jsut defered to write time. + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + try { + fory.serialize(String.class.getPackage()); + Assert.fail("Expected serialization of java.lang.Package to fail"); + } catch (SerializationException e) { + Assert.assertTrue(e.getCause() instanceof UnsupportedOperationException); + Assert.assertTrue(e.getMessage().contains("doesn't support serialization")); + } + } + } From 92f555e46f4f280272397bbd76af2719af0aff41 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sat, 27 Jun 2026 01:51:19 -0400 Subject: [PATCH 04/15] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../apache/fory/platform/GraalvmSupport.java | 2 ++ .../apache/fory/resolver/ClassResolver.java | 6 +++--- .../serializer/NonSerializableSerializer.java | 4 ++-- .../java/org/apache/fory/ForyCopyTest.java | 20 +++---------------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java index de036f9759..c054f1cec3 100644 --- a/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java @@ -39,6 +39,7 @@ import org.apache.fory.serializer.JavaSerializer; import org.apache.fory.serializer.JdkProxySerializer; import org.apache.fory.serializer.LambdaSerializer; +import org.apache.fory.serializer.NonSerializableSerializer; import org.apache.fory.serializer.ObjectSerializer; import org.apache.fory.serializer.ObjectStreamSerializer; import org.apache.fory.serializer.PrimitiveArraySerializers; @@ -140,6 +141,7 @@ public class GraalvmSupport { registerDefaultSerializerClass(ObjectStreamSerializer.class); registerDefaultSerializerClass(JavaSerializer.class); registerDefaultSerializerClass(ObjectSerializer.class); + registerDefaultSerializerClass(NonSerializableSerializer.class); } /** Returns true if current process is running in graalvm native image build stage. */ diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index b7b1bfb9f5..c4b31a14c3 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -181,7 +181,7 @@ */ @NotThreadSafe @SuppressWarnings({"rawtypes", "unchecked"}) -public class ClassResolver extends TypeResolver { +public class ClassResolver extends TypeResolver { private static final Logger LOG = LoggerFactory.getLogger(ClassResolver.class); public static final int NATIVE_START_ID = Types.BOUND; @@ -1474,9 +1474,9 @@ public Class getSerializerClass(Class cls, boolean code if (config.checkJdkClassSerializable()) { if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))){ // Issue #2941: previously threw UnsupportedOperationException here, which also broke - // Fory.copy() for non-serlializble JDK classes (e.g. java.lang.Package). Route to a + // Fory.copy() for non-serializable JDK classes (e.g. java.lang.Package). Route to a // serializer that still refuses binary serialization (write/read throw) but - // supports copy() via reflective field copy + // supports field copy via AbstractObjectSerializer return NonSerializableSerializer.class; } } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index d8bff538b7..0ead42eb6a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -28,8 +28,8 @@ * *

Binary serialization remains unsupported — {@link #write} and {@link #read} throw * {@link UnsupportedOperationException}, preserving the prior behavior of {@code ClassResolver}'s - * JDK-serializable guard. However {@code copy} is supported via the reflection-based field copy - * inherited from {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs + * JDK-serializable guard. However {@code copy} is supported via the field copy via + * {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs * that transitively contain such classes (issue #2941). */ public final class NonSerializableSerializer extends AbstractObjectSerializer { diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 7edafc2ee3..393347f4cb 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -469,28 +469,14 @@ public void testCopyNonSerializableJdkClass(){ Package pkg = String.class.getPackage(); Package copy = fory.copy(pkg); Assert.assertNotNull(copy); + Assert.assertEquals(copy.getName(), pkg.getName()); } - @Test - public void testCopyHashMapStillWorks(){ - // Regression for chaokunyang's concern (comments on issue #2941): HashMap keeps - //essential state (size, table) in transient field. The copy path copies all fields - // including transient, so this must still produce an equal map. - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .requireClassRegistration(false) - .build(); - Map map = new HashMap<>(); - map.put("a", 1); - map.put("b", 2); - Map copy = fory.copy(map); - Assert.assertEquals(map, copy); - } @Test public void testSerializeNonSerializableJdkClassStillThrows(){ - // Regression guard: we must not have weakened serialization. Serliazing a - // non-serliazable JDK class must still throw, jsut defered to write time. + // Regression guard: we must not have weakened serialization. Serializing a + // non-serializable JDK class must still throw, just deferred to write time. Fory fory = Fory.builder() .withLanguage(Language.JAVA) From 80d3b784dfde701203667111c04d5e478e90fbc4 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sat, 27 Jun 2026 02:26:30 -0400 Subject: [PATCH 05/15] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../apache/fory/resolver/ClassResolver.java | 31 +++++++++++++++++-- .../serializer/NonSerializableSerializer.java | 27 ++++++++-------- .../java/org/apache/fory/ForyCopyTest.java | 24 +++++++------- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index c4b31a14c3..f0bec1585b 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -114,10 +114,37 @@ import org.apache.fory.platform.AndroidSupport; import org.apache.fory.platform.GraalvmSupport; import org.apache.fory.reflect.ReflectionUtils; -import org.apache.fory.serializer.*; +import org.apache.fory.serializer.ArraySerializers; +import org.apache.fory.serializer.BufferSerializers; +import org.apache.fory.serializer.CompatibleSerializer; +import org.apache.fory.serializer.CopyOnlyObjectSerializer; +import org.apache.fory.serializer.EnumSerializer; +import org.apache.fory.serializer.ExceptionSerializers; +import org.apache.fory.serializer.ExternalizableSerializer; +import org.apache.fory.serializer.ForyCopyableSerializer; +import org.apache.fory.serializer.JavaSerializer; +import org.apache.fory.serializer.JdkProxySerializer; +import org.apache.fory.serializer.LambdaSerializer; +import org.apache.fory.serializer.LocaleSerializer; +import org.apache.fory.serializer.NonSerializableSerializer; import org.apache.fory.serializer.CodegenSerializer.LazyInitBeanSerializer; +import org.apache.fory.serializer.NoneSerializer; +import org.apache.fory.serializer.ObjectSerializer; +import org.apache.fory.serializer.OptionalSerializers; +import org.apache.fory.serializer.PrimitiveArraySerializers; +import org.apache.fory.serializer.PrimitiveSerializers; +import org.apache.fory.serializer.ReplaceResolveSerializer; +import org.apache.fory.serializer.SerializedLambdaSerializer; +import org.apache.fory.serializer.Serializer; +import org.apache.fory.serializer.Serializers; +import org.apache.fory.serializer.Shareable; +import org.apache.fory.serializer.SqlTimeSerializers; +import org.apache.fory.serializer.TimeSerializers; +import org.apache.fory.serializer.UnknownClass; import org.apache.fory.serializer.UnknownClass.UnknownEmptyStruct; import org.apache.fory.serializer.UnknownClass.UnknownStruct; +import org.apache.fory.serializer.UnknownClassSerializers; +import org.apache.fory.serializer.UnsignedSerializers; import org.apache.fory.serializer.collection.ChildContainerSerializers; import org.apache.fory.serializer.collection.CollectionLikeSerializer; import org.apache.fory.serializer.collection.CollectionSerializer; @@ -1472,7 +1499,7 @@ public Class getSerializerClass(Class cls, boolean code } } if (config.checkJdkClassSerializable()) { - if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))){ + if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)){ // Issue #2941: previously threw UnsupportedOperationException here, which also broke // Fory.copy() for non-serializable JDK classes (e.g. java.lang.Package). Route to a // serializer that still refuses binary serialization (write/read throw) but diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index 0ead42eb6a..bc3606d50a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -33,20 +33,19 @@ * that transitively contain such classes (issue #2941). */ public final class NonSerializableSerializer extends AbstractObjectSerializer { + public NonSerializableSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type); + } - public NonSerializableSerializer(TypeResolver typeResolver, Class type) { - super(typeResolver, type); - } + @Override + public void write(WriteContext writeContext, T value) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type.getName())); + } - @Override - public void write(WriteContext writeContext, T value) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type)); - } - - @Override - public T read(ReadContext readContext) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type)); - } + @Override + public T read(ReadContext readContext) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type.getName())); + } } diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 393347f4cb..b0035b7d8b 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -459,22 +459,22 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass(){ - // Issue #2941: copying an object that is (or contains) a non-serializable JDK class - // like java.lang.Package used to throw. It should now succeed. - - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .requireClassRegistration(false) - .build(); - Package pkg = String.class.getPackage(); - Package copy = fory.copy(pkg); - Assert.assertNotNull(copy); - Assert.assertEquals(copy.getName(), pkg.getName()); + // Issue #2941: copying an object that is (or contains) a non-serializable JDK class + // like java.lang.Package used to throw. It should now succeed. + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + Package pkg = String.class.getPackage(); + Package copy = fory.copy(pkg); + Assert.assertNotNull(copy); + Assert.assertEquals(copy.getName(), pkg.getName()); } @Test - public void testSerializeNonSerializableJdkClassStillThrows(){ + public void testSerializeNonSerializableJdkClassStillThrows() { // Regression guard: we must not have weakened serialization. Serializing a // non-serializable JDK class must still throw, just deferred to write time. From efd68664897816fe281b2c3f6037ede4b7da7351 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 28 Jun 2026 01:21:00 -0400 Subject: [PATCH 06/15] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../org/apache/fory/resolver/ClassResolver.java | 4 +--- .../fory/serializer/NonSerializableSerializer.java | 13 +++++++------ .../src/test/java/org/apache/fory/ForyCopyTest.java | 5 +---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index f0bec1585b..09eed73d36 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -1500,9 +1500,7 @@ public Class getSerializerClass(Class cls, boolean code } if (config.checkJdkClassSerializable()) { if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)){ - // Issue #2941: previously threw UnsupportedOperationException here, which also broke - // Fory.copy() for non-serializable JDK classes (e.g. java.lang.Package). Route to a - // serializer that still refuses binary serialization (write/read throw) but + // Route to a serializer that still refuses binary serialization (write/read throw) but // supports field copy via AbstractObjectSerializer return NonSerializableSerializer.class; } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index bc3606d50a..8fa9d45917 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -24,13 +24,14 @@ import org.apache.fory.resolver.TypeResolver; /** - * Serializer for non-Serializable JDK classes (e.g. {@code java.lang.Package}). + * Serializer for non-Serializable JDK classes. Binary serialization is unsupported — + * {@link #write}/{@link #read} throw {@link UnsupportedOperationException} — while copy + * reuses {@link AbstractObjectSerializer}'s field-copy implementation. * - *

Binary serialization remains unsupported — {@link #write} and {@link #read} throw - * {@link UnsupportedOperationException}, preserving the prior behavior of {@code ClassResolver}'s - * JDK-serializable guard. However {@code copy} is supported via the field copy via - * {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs - * that transitively contain such classes (issue #2941). + *

Distinct from {@link CopyOnlyObjectSerializer}, which blocks serialization for + * registration-security reasons (remediable by registering the class). Here serialization + * is intrinsically unsupported and not remediable by registration, so the failure semantics + * differ deliberately. */ public final class NonSerializableSerializer extends AbstractObjectSerializer { public NonSerializableSerializer(TypeResolver typeResolver, Class type) { diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index b0035b7d8b..68442e9d4d 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -459,8 +459,6 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass(){ - // Issue #2941: copying an object that is (or contains) a non-serializable JDK class - // like java.lang.Package used to throw. It should now succeed. Fory fory = Fory.builder() .withLanguage(Language.JAVA) @@ -475,8 +473,7 @@ public void testCopyNonSerializableJdkClass(){ @Test public void testSerializeNonSerializableJdkClassStillThrows() { - // Regression guard: we must not have weakened serialization. Serializing a - // non-serializable JDK class must still throw, just deferred to write time. + // Serializing anon-serializable JDK class must still throw, just deferred to write time. Fory fory = Fory.builder() .withLanguage(Language.JAVA) From 55be440fa36c8e7b2ec30ae109c6dcdb1931f3bc Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 14:34:49 -0400 Subject: [PATCH 07/15] style(java): apply spotlless formatting and fix checkstyle violations --- .../src/main/java/org/apache/fory/resolver/ClassResolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index 09eed73d36..856a4ac447 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -116,6 +116,7 @@ import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.serializer.ArraySerializers; import org.apache.fory.serializer.BufferSerializers; +import org.apache.fory.serializer.CodegenSerializer.LazyInitBeanSerializer; import org.apache.fory.serializer.CompatibleSerializer; import org.apache.fory.serializer.CopyOnlyObjectSerializer; import org.apache.fory.serializer.EnumSerializer; @@ -127,7 +128,6 @@ import org.apache.fory.serializer.LambdaSerializer; import org.apache.fory.serializer.LocaleSerializer; import org.apache.fory.serializer.NonSerializableSerializer; -import org.apache.fory.serializer.CodegenSerializer.LazyInitBeanSerializer; import org.apache.fory.serializer.NoneSerializer; import org.apache.fory.serializer.ObjectSerializer; import org.apache.fory.serializer.OptionalSerializers; @@ -1499,7 +1499,7 @@ public Class getSerializerClass(Class cls, boolean code } } if (config.checkJdkClassSerializable()) { - if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)){ + if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)) { // Route to a serializer that still refuses binary serialization (write/read throw) but // supports field copy via AbstractObjectSerializer return NonSerializableSerializer.class; From 451b2b0cdd32f9847a74ccb1ec3380d25259f2b4 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 14:53:24 -0400 Subject: [PATCH 08/15] test(java): enable ref copy for non-serializable JDK class copy test --- java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 68442e9d4d..757759c602 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -477,6 +477,7 @@ public void testSerializeNonSerializableJdkClassStillThrows() { Fory fory = Fory.builder() .withLanguage(Language.JAVA) + .withRefCopy(true) .requireClassRegistration(false) .build(); try { From 2394debf9acefd896df232e3dd000b37f8a99466 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 16:32:39 -0400 Subject: [PATCH 09/15] test(java): enable ref copy for non-serializable JDK class copy test --- java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 757759c602..9b43102d59 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -462,6 +462,7 @@ public void testCopyNonSerializableJdkClass(){ Fory fory = Fory.builder() .withLanguage(Language.JAVA) + .withRefCopy(true) .requireClassRegistration(false) .build(); Package pkg = String.class.getPackage(); From 23663fd1e1894c455ac44b574ca4dcd1e581cc0b Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 18:23:25 -0400 Subject: [PATCH 10/15] test(java): assert non-serializable JDK class routes to NonSerializableSerializer --- .../serializer/NonSerializableSerializer.java | 2 ++ .../java/org/apache/fory/ForyCopyTest.java | 21 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index 8fa9d45917..26981dd4d9 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -35,9 +35,11 @@ */ public final class NonSerializableSerializer extends AbstractObjectSerializer { public NonSerializableSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type); } + @Override public void write(WriteContext writeContext, T value) { throw new UnsupportedOperationException( diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 9b43102d59..23992368d1 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -88,6 +88,7 @@ import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.EnumSerializerTest; import org.apache.fory.serializer.EnumSerializerTest.EnumFoo; +import org.apache.fory.serializer.NonSerializableSerializer; import org.apache.fory.serializer.Serializer; import org.apache.fory.serializer.collection.ChildContainerSerializersTest.ChildArrayDeque; import org.apache.fory.serializer.collection.SynchronizedSerializersTest; @@ -458,17 +459,15 @@ public void testComplexCollectionCopy() { } @Test - public void testCopyNonSerializableJdkClass(){ - - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .withRefCopy(true) - .requireClassRegistration(false) - .build(); - Package pkg = String.class.getPackage(); - Package copy = fory.copy(pkg); - Assert.assertNotNull(copy); - Assert.assertEquals(copy.getName(), pkg.getName()); + public void testCopyNonSerializableJdkClass() { + Fory fory = + Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + Class serializerClass = + fory.getTypeResolver().getSerializerClass(java.lang.Package.class); + Assert.assertSame(serializerClass, NonSerializableSerializer.class); } From b05f5f5c813c62aba1bdb39004889e02249a6c8e Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 19:25:35 -0400 Subject: [PATCH 11/15] style(java): apply google-java-format to serializer and copy test --- .../serializer/NonSerializableSerializer.java | 13 ++++---- .../java/org/apache/fory/ForyCopyTest.java | 30 +++++++------------ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index 26981dd4d9..39d5175568 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -34,21 +34,18 @@ * differ deliberately. */ public final class NonSerializableSerializer extends AbstractObjectSerializer { - public NonSerializableSerializer(TypeResolver typeResolver, Class type) { - - super(typeResolver, type); - } + public NonSerializableSerializer(TypeResolver typeResolver, Class type) { super(typeResolver, type); } @Override public void write(WriteContext writeContext, T value) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type.getName())); + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type.getName())); } @Override public T read(ReadContext readContext) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type.getName())); + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type.getName())); } } diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 23992368d1..3bdbd5719c 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -460,33 +460,23 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass() { - Fory fory = - Fory.builder() - .withLanguage(Language.JAVA) - .requireClassRegistration(false) - .build(); + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); Class serializerClass = - fory.getTypeResolver().getSerializerClass(java.lang.Package.class); + fory.getTypeResolver().getSerializerClass(java.lang.Package.class); Assert.assertSame(serializerClass, NonSerializableSerializer.class); } @Test public void testSerializeNonSerializableJdkClassStillThrows() { - // Serializing anon-serializable JDK class must still throw, just deferred to write time. - - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .withRefCopy(true) - .requireClassRegistration(false) - .build(); - try { - fory.serialize(String.class.getPackage()); - Assert.fail("Expected serialization of java.lang.Package to fail"); - } catch (SerializationException e) { - Assert.assertTrue(e.getCause() instanceof UnsupportedOperationException); - Assert.assertTrue(e.getMessage().contains("doesn't support serialization")); - } + Fory fory = Fory.builder().withLanguage(Language.JAVA).withRefCopy(true).requireClassRegistration(false).build(); + try { + fory.serialize(String.class.getPackage()); + Assert.fail("Expected serialization of java.lang.Package to fail"); + } catch (SerializationException e) { + Assert.assertTrue(e.getCause() instanceof UnsupportedOperationException); + Assert.assertTrue(e.getMessage().contains("doesn't support serialization")); + } } } From 4c454e9f28f4a354ebd91bafe1af6a85121e6c6f Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 19:34:21 -0400 Subject: [PATCH 12/15] style(java): apply google-java-format to classresolver --- .../src/main/java/org/apache/fory/resolver/ClassResolver.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index 856a4ac447..b72a5478ba 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -1500,8 +1500,6 @@ public Class getSerializerClass(Class cls, boolean code } if (config.checkJdkClassSerializable()) { if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)) { - // Route to a serializer that still refuses binary serialization (write/read throw) but - // supports field copy via AbstractObjectSerializer return NonSerializableSerializer.class; } } From 90e36a71316f806cb210d8d2392dfa1f9d989ef1 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 19:42:17 -0400 Subject: [PATCH 13/15] style(java): apply google-java-format to classresolver --- .../src/main/java/org/apache/fory/resolver/ClassResolver.java | 4 +--- .../fory-core/src/test/java/org/apache/fory/ForyCopyTest.java | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index b72a5478ba..5a86cd7093 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -1500,7 +1500,7 @@ public Class getSerializerClass(Class cls, boolean code } if (config.checkJdkClassSerializable()) { if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)) { - return NonSerializableSerializer.class; + return NonSerializableSerializer.class; } } if (ScalaTypes.SCALA_AVAILABLE && ReflectionUtils.isScalaSingletonObject(cls)) { @@ -1513,8 +1513,6 @@ public Class getSerializerClass(Class cls, boolean code } } if (isCollection(cls)) { - // Serializer of common collection such as ArrayList/LinkedList should be registered - // already. serializerClass = ChildContainerSerializers.getCollectionSerializerClass(cls); if (serializerClass != null) { return serializerClass; diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 3bdbd5719c..c30341926f 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -461,8 +461,7 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass() { Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); - Class serializerClass = - fory.getTypeResolver().getSerializerClass(java.lang.Package.class); + Class serializerClass = fory.getTypeResolver().getSerializerClass(java.lang.Package.class); Assert.assertSame(serializerClass, NonSerializableSerializer.class); } From eb3a34920830d0fafdba6669936b76bc09b68ce8 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 20:39:40 -0400 Subject: [PATCH 14/15] (style): fix spotless formatting --- .../serializer/NonSerializableSerializer.java | 21 ++++++++++--------- .../java/org/apache/fory/ForyCopyTest.java | 17 +++++++++------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index 39d5175568..497cc9cb18 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -24,28 +24,29 @@ import org.apache.fory.resolver.TypeResolver; /** - * Serializer for non-Serializable JDK classes. Binary serialization is unsupported — - * {@link #write}/{@link #read} throw {@link UnsupportedOperationException} — while copy - * reuses {@link AbstractObjectSerializer}'s field-copy implementation. + * Serializer for non-Serializable JDK classes. Binary serialization is unsupported — {@link + * #write}/{@link #read} throw {@link UnsupportedOperationException} — while copy reuses {@link + * AbstractObjectSerializer}'s field-copy implementation. * *

Distinct from {@link CopyOnlyObjectSerializer}, which blocks serialization for - * registration-security reasons (remediable by registering the class). Here serialization - * is intrinsically unsupported and not remediable by registration, so the failure semantics - * differ deliberately. + * registration-security reasons (remediable by registering the class). Here serialization is + * intrinsically unsupported and not remediable by registration, so the failure semantics differ + * deliberately. */ public final class NonSerializableSerializer extends AbstractObjectSerializer { - public NonSerializableSerializer(TypeResolver typeResolver, Class type) { super(typeResolver, type); } - + public NonSerializableSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type); + } @Override public void write(WriteContext writeContext, T value) { throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type.getName())); + String.format("Class %s doesn't support serialization.", type.getName())); } @Override public T read(ReadContext readContext) { throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type.getName())); + String.format("Class %s doesn't support serialization.", type.getName())); } } diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index c30341926f..2b5478bbd0 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -460,15 +460,21 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass() { - Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); - Class serializerClass = fory.getTypeResolver().getSerializerClass(java.lang.Package.class); - Assert.assertSame(serializerClass, NonSerializableSerializer.class); + Fory fory = + Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + Class serializerClass = + fory.getTypeResolver().getSerializerClass(java.lang.Package.class); + Assert.assertSame(serializerClass, NonSerializableSerializer.class); } - @Test public void testSerializeNonSerializableJdkClassStillThrows() { - Fory fory = Fory.builder().withLanguage(Language.JAVA).withRefCopy(true).requireClassRegistration(false).build(); + Fory fory = + Fory.builder() + .withLanguage(Language.JAVA) + .withRefCopy(true) + .requireClassRegistration(false) + .build(); try { fory.serialize(String.class.getPackage()); Assert.fail("Expected serialization of java.lang.Package to fail"); @@ -477,5 +483,4 @@ public void testSerializeNonSerializableJdkClassStillThrows() { Assert.assertTrue(e.getMessage().contains("doesn't support serialization")); } } - } From 7608268efb0a2305a040d5d680f519642518d2d5 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 12 Jul 2026 20:45:32 -0400 Subject: [PATCH 15/15] style(java): apply spotless formatting --- java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 2b5478bbd0..8c7685ad12 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -460,8 +460,7 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass() { - Fory fory = - Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build(); Class serializerClass = fory.getTypeResolver().getSerializerClass(java.lang.Package.class); Assert.assertSame(serializerClass, NonSerializableSerializer.class);