This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new a3095c43e feat(java): reduce java serializer memory usage by share 
across Fory instances (#3546)
a3095c43e is described below

commit a3095c43e4c70e040aa59d48c3c88ebca75ac801
Author: Shawn Yang <[email protected]>
AuthorDate: Thu Apr 9 22:06:29 2026 +0800

    feat(java): reduce java serializer memory usage by share across Fory 
instances (#3546)
    
    ## Why?
    
    Reduce Java serializer memory usage by reusing safe serializer instances
    across equivalent Fory runtimes, and make the shareability contract
    explicit in the serializer API.
    
    ## What does this PR do?
    
    - Replaces `Serializer.shareable()` with a new `Shareable` marker
    interface and updates the serializer API/docs to use that contract.
    - Migrates the eligible Java, Kotlin, and Scala serializers that are
    safe to reuse to implement `Shareable`.
    - Caches explicitly registered shareable serializers and their
    `TypeInfo` in `SharedRegistry` so equivalent runtimes can reuse them
    through `registerSerializer` and `registerInternalSerializer` without
    changing lazy serializer creation.
    - Keeps same-runtime serializer overrides local instead of publishing
    them into `SharedRegistry`.
    - Aligns xlang named-type lookup and unsigned serializer registration
    with the shared serializer path.
    - Adds focused Java and xlang tests for shared serializer reuse and
    override behavior across equivalent runtimes.
    
    ## Related issues
    
    N/A
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [x] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    N/A
---
 .agents/languages/java.md                          |   5 +
 docs/guide/java/custom-serializers.md              |  21 ++--
 .../org/apache/fory/resolver/ClassResolver.java    |  68 ++++++++-----
 .../org/apache/fory/resolver/SharedRegistry.java   |  37 ++++++-
 .../org/apache/fory/resolver/TypeResolver.java     |  40 +-------
 .../org/apache/fory/resolver/XtypeResolver.java    | 113 +++++++++++++--------
 .../apache/fory/serializer/ArraySerializers.java   |   8 +-
 .../apache/fory/serializer/BufferSerializers.java  |   8 +-
 .../org/apache/fory/serializer/EnumSerializer.java |   7 +-
 .../apache/fory/serializer/Float16Serializer.java  |   7 +-
 .../apache/fory/serializer/LocaleSerializer.java   |   7 +-
 .../fory/serializer/OptionalSerializers.java       |  24 ++---
 .../fory/serializer/PrimitiveSerializers.java      | 100 ++++--------------
 .../org/apache/fory/serializer/Serializer.java     |  40 ++------
 .../org/apache/fory/serializer/Serializers.java    | 103 +++++--------------
 .../java/org/apache/fory/serializer/Shareable.java |  92 +++++++++++++++++
 .../apache/fory/serializer/TimeSerializers.java    | 111 +++++---------------
 .../fory/serializer/UnknownClassSerializers.java   |   8 +-
 .../fory/serializer/UnsignedSerializers.java       |  32 ++----
 .../collection/PrimitiveListSerializers.java       |   9 +-
 .../fory/type/unsigned/UnsignedSerializers.java    | 103 ++-----------------
 .../apache/fory/resolver/ClassResolverTest.java    |  55 ++++++++++
 .../java/org/apache/fory/xlang/RegisterTest.java   |  50 +++++++++
 .../extension/serializer/ByteStringSerializer.java |   8 +-
 .../extension/serializer/ProtobufSerializer.java   |   8 +-
 .../fory/serializer/kotlin/DurationSerializer.kt   |   5 +-
 .../serializer/kotlin/UnsignedArraySerializers.kt  |   5 +-
 .../fory/serializer/kotlin/UnsignedSerializer.kt   |  41 +-------
 .../fory/serializer/kotlin/UuidSerializer.kt       |   6 +-
 .../serializer/scala/ToFactorySerializers.java     |  15 +--
 .../fory/serializer/scala/RangeSerializer.scala    |  13 +--
 31 files changed, 504 insertions(+), 645 deletions(-)

diff --git a/.agents/languages/java.md b/.agents/languages/java.md
index e55f12e22..a1e42dc30 100644
--- a/.agents/languages/java.md
+++ b/.agents/languages/java.md
@@ -7,6 +7,7 @@ Load this file when changing anything under `java/` or when 
Java drives a cross-
 - Run all Maven commands from within `java/`.
 - Changes under `java/` must pass code style checks and tests.
 - Fory Java requires JDK `17+`.
+- Run Java `spotless` with JDK `21+`. If the current runtime is lower than 21, 
export `JAVA_HOME` to a JDK 21 installation before running `mvn spotless:check` 
or `mvn spotless:apply`.
 - `fory-core` targets Java 8 bytecode and `fory-format` targets Java 11 
bytecode. Do not use newer APIs in those modules.
 - Do not use wildcard imports.
 - If you run temporary tests with `java -cp`, run `mvn -T16 install 
-DskipTests` first so local Fory jars are current.
@@ -44,6 +45,10 @@ mvn -T16 spotless:check
 # Apply formatting
 mvn -T16 spotless:apply
 
+# Spotless with JDK 21 when the current runtime is older
+export JAVA_HOME=$(/usr/libexec/java_home -v 21)
+mvn -T16 spotless:check
+
 # Style check
 mvn -T16 checkstyle:check
 
diff --git a/docs/guide/java/custom-serializers.md 
b/docs/guide/java/custom-serializers.md
index a1aee2f60..34bd73750 100644
--- a/docs/guide/java/custom-serializers.md
+++ b/docs/guide/java/custom-serializers.md
@@ -27,8 +27,8 @@ Custom serializers should not retain `Fory`.
 
 - Use `Config` when the serializer only depends on immutable configuration and 
can be shared.
 - Use `TypeResolver` when the serializer needs type metadata, generics, or 
nested dynamic dispatch.
-- If a serializer retains `TypeResolver`, it is usually not shareable and 
should keep the default
-  `shareable() == false`.
+- If a serializer retains `TypeResolver`, it is usually not shareable and 
should not implement
+  `Shareable`.
 
 ## Basic Serializer
 
@@ -41,8 +41,9 @@ import org.apache.fory.context.ReadContext;
 import org.apache.fory.context.WriteContext;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.serializer.Serializer;
+import org.apache.fory.serializer.Shareable;
 
-public final class FooSerializer extends Serializer<Foo> {
+public final class FooSerializer extends Serializer<Foo> implements Shareable {
   public FooSerializer(Config config) {
     super(config, Foo.class);
   }
@@ -61,11 +62,6 @@ public final class FooSerializer extends Serializer<Foo> {
     foo.f2 = readContext.readString(buffer);
     return foo;
   }
-
-  @Override
-  public boolean shareable() {
-    return true;
-  }
 }
 ```
 
@@ -108,7 +104,7 @@ public final class EnvelopeSerializer extends 
Serializer<Envelope> {
 }
 ```
 
-This serializer can be shareable because it retains no runtime-local mutable 
state.
+This serializer can implement `Shareable` because it retains no runtime-local 
mutable state.
 
 ## Collection Serializers
 
@@ -218,9 +214,10 @@ fory.registerSerializer(
 
 ## Shareability
 
-Override `shareable()` only when the serializer can be safely reused across 
equivalent runtimes and
-concurrent operations. In practice, that means the serializer must not retain 
operation state,
-runtime-local mutable state, or mutable scratch buffers that are shared across 
calls.
+Implement the `Shareable` marker interface when the serializer can be safely 
reused across
+equivalent runtimes and concurrent operations. A shareable serializer must not 
retain operation
+state, runtime-local mutable state, or mutable scratch buffers shared across 
calls. Consumers can
+check shareability via `serializer instanceof Shareable`.
 
 In practice:
 
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 6e0ecd6b3..3a7dbbd2c 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
@@ -126,6 +126,7 @@ import 
org.apache.fory.serializer.SerializedLambdaSerializer;
 import org.apache.fory.serializer.Serializer;
 import org.apache.fory.serializer.SerializerFactory;
 import org.apache.fory.serializer.Serializers;
+import org.apache.fory.serializer.Shareable;
 import org.apache.fory.serializer.TimeSerializers;
 import org.apache.fory.serializer.UnknownClass;
 import org.apache.fory.serializer.UnknownClass.UnknownEmptyStruct;
@@ -705,24 +706,25 @@ public class ClassResolver extends TypeResolver {
 
   @Override
   public boolean isRegistered(Class<?> cls) {
-    return isRegisteredByIdLocalOrFrozen(cls) || 
isRegisteredByNameLocalOrFrozen(cls);
+    return extRegistry.registeredClassIdMap.get(cls) != null
+        || extRegistry.registeredClasses.inverse().get(cls) != null;
   }
 
   public boolean isRegisteredByName(String name) {
-    return isRegisteredByNameLocalOrFrozen(name);
+    return extRegistry.registeredClasses.get(name) != null;
   }
 
   @Override
   public boolean isRegisteredByName(Class<?> cls) {
-    return isRegisteredByNameLocalOrFrozen(cls);
+    return extRegistry.registeredClasses.inverse().get(cls) != null;
   }
 
   public String getRegisteredName(Class<?> cls) {
-    return getRegisteredNameLocalOrFrozen(cls);
+    return extRegistry.registeredClasses.inverse().get(cls);
   }
 
   public Tuple2<String, String> getRegisteredNameTuple(Class<?> cls) {
-    String name = getRegisteredNameLocalOrFrozen(cls);
+    String name = extRegistry.registeredClasses.inverse().get(cls);
     Preconditions.checkNotNull(name);
     int index = name.lastIndexOf(".");
     if (index != -1) {
@@ -734,11 +736,11 @@ public class ClassResolver extends TypeResolver {
 
   @Override
   public boolean isRegisteredById(Class<?> cls) {
-    return isRegisteredByIdLocalOrFrozen(cls);
+    return extRegistry.registeredClassIdMap.get(cls) != null;
   }
 
   public Integer getRegisteredClassId(Class<?> cls) {
-    return getRegisteredClassIdLocalOrFrozen(cls);
+    return extRegistry.registeredClassIdMap.get(cls);
   }
 
   public Class<?> getRegisteredClass(short id) {
@@ -752,7 +754,7 @@ public class ClassResolver extends TypeResolver {
   }
 
   public Class<?> getRegisteredClass(String className) {
-    return getRegisteredClassLocalOrFrozen(className);
+    return extRegistry.registeredClasses.get(className);
   }
 
   public Class<?> getRegisteredClassByTypeId(int typeId) {
@@ -795,11 +797,11 @@ public class ClassResolver extends TypeResolver {
   }
 
   public String getTypeAlias(Class<?> cls) {
-    Integer id = getRegisteredClassIdLocalOrFrozen(cls);
+    Integer id = extRegistry.registeredClassIdMap.get(cls);
     if (id != null) {
       return Integer.toUnsignedString(id);
     }
-    String name = getRegisteredNameLocalOrFrozen(cls);
+    String name = extRegistry.registeredClasses.inverse().get(cls);
     if (name != null) {
       return name;
     }
@@ -815,7 +817,7 @@ public class ClassResolver extends TypeResolver {
     if (typeInfo != null) {
       return typeInfo.typeId;
     }
-    Integer classId = getRegisteredClassIdLocalOrFrozen(cls);
+    Integer classId = extRegistry.registeredClassIdMap.get(cls);
     if (classId != null) {
       typeInfo = classInfoMap.get(cls);
       if (typeInfo == null) {
@@ -843,7 +845,7 @@ public class ClassResolver extends TypeResolver {
     if (typeInfo != null) {
       return typeInfo.userTypeId;
     }
-    Integer classId = getRegisteredClassIdLocalOrFrozen(cls);
+    Integer classId = extRegistry.registeredClassIdMap.get(cls);
     if (classId != null && !isInternalRegisteredClassId(cls, classId)) {
       return classId;
     }
@@ -977,7 +979,7 @@ public class ClassResolver extends TypeResolver {
   public <T> void registerSerializer(Class<T> type, Class<? extends 
Serializer> serializerClass) {
     checkRegisterAllowed();
     checkSerializerRegistration(type, serializerClass);
-    registerSerializer(type, Serializers.newSerializer(this, type, 
serializerClass));
+    registerSerializerImpl(type, Serializers.newSerializer(this, type, 
serializerClass));
   }
 
   @Override
@@ -998,7 +1000,7 @@ public class ClassResolver extends TypeResolver {
    */
   @Override
   public void registerInternalSerializer(Class<?> type, Serializer<?> 
serializer) {
-    Integer classId = getRegisteredClassIdLocalOrFrozen(type);
+    Integer classId = extRegistry.registeredClassIdMap.get(type);
     if (classId != null && !isInternalRegisteredClassId(type, classId)) {
       throw new IllegalArgumentException(
           String.format(
@@ -1022,9 +1024,29 @@ public class ClassResolver extends TypeResolver {
     if 
(!serializer.getClass().getPackage().getName().startsWith("org.apache.fory")) {
       SerializationUtils.validate(type, serializer.getClass());
     }
+    TypeInfo existingTypeInfo = classInfoMap.get(type);
+    boolean localOverride = existingTypeInfo != null && 
existingTypeInfo.serializer != null;
+    boolean shareable = serializer instanceof Shareable;
+    if (shareable && !localOverride) {
+      serializer = sharedRegistry.cacheRegisteredSerializer(type, serializer);
+    }
     addSerializer(type, serializer);
     TypeInfo typeInfo = classInfoMap.get(type);
-    classInfoMap.put(type, typeInfo);
+    if (shareable && !localOverride) {
+      TypeInfo sharedTypeInfo = sharedRegistry.cacheRegisteredTypeInfo(type, 
typeInfo);
+      if (sharedTypeInfo != typeInfo) {
+        typeInfo = sharedTypeInfo;
+        updateTypeInfo(type, typeInfo);
+        if (typeInfo.namespace != null && typeInfo.typeName != null) {
+          TypeNameBytes typeNameBytes =
+              new TypeNameBytes(typeInfo.namespace.hash, 
typeInfo.typeName.hash);
+          compositeNameBytes2TypeInfo.put(typeNameBytes, typeInfo);
+        }
+        if (typeInfoCache.type == type) {
+          typeInfoCache = NIL_TYPE_INFO;
+        }
+      }
+    }
     // in order to support customized serializer for abstract or interface.
     if (!type.isPrimitive() && (ReflectionUtils.isAbstract(type) || 
type.isInterface())) {
       extRegistry.abstractTypeInfo.put(type, typeInfo);
@@ -1081,7 +1103,7 @@ public class ClassResolver extends TypeResolver {
   /** Set serializer for class whose name is {@code className}. */
   public void setSerializer(String className, Class<? extends Serializer> 
serializer) {
     for (Map.Entry<Class<?>, TypeInfo> entry : classInfoMap.iterable()) {
-      if (isRegisteredByNameLocalOrFrozen(className)) {
+      if (extRegistry.registeredClasses.get(className) != null) {
         LOG.warn("Skip clear serializer for registered class {}", className);
         return;
       }
@@ -1100,7 +1122,7 @@ public class ClassResolver extends TypeResolver {
     for (Map.Entry<Class<?>, TypeInfo> entry : classInfoMap.iterable()) {
       Class<?> cls = entry.getKey();
       String className = cls.getName();
-      if (isRegisteredByNameLocalOrFrozen(className)) {
+      if (extRegistry.registeredClasses.get(className) != null) {
         continue;
       }
       if (className.startsWith(classNamePrefix)) {
@@ -1137,7 +1159,7 @@ public class ClassResolver extends TypeResolver {
   public void addSerializer(Class<?> type, Serializer<?> serializer) {
     Preconditions.checkNotNull(serializer);
     TypeInfo typeInfo;
-    Integer classId = getRegisteredClassIdLocalOrFrozen(type);
+    Integer classId = extRegistry.registeredClassIdMap.get(type);
     boolean registered = classId != null;
     if (registered) {
       int id = classId;
@@ -1526,8 +1548,8 @@ public class ClassResolver extends TypeResolver {
       if (!config.suppressClassRegistrationWarnings()
           && !Functions.isLambda(cls)
           && !ReflectionUtils.isJdkProxy(cls)
-          && !isRegisteredByIdLocalOrFrozen(cls)
-          && !isRegisteredByNameLocalOrFrozen(cls)
+          && extRegistry.registeredClassIdMap.get(cls) == null
+          && extRegistry.registeredClasses.inverse().get(cls) == null
           && !hasGraalvmSerializerClass(cls)
           && !shimDispatcher.contains(cls)
           && !extRegistry.isTypeCheckerSet()) {
@@ -1677,7 +1699,7 @@ public class ClassResolver extends TypeResolver {
   }
 
   private boolean isSecure(Class<?> cls) {
-    if (isRegisteredByNameLocalOrFrozen(cls)
+    if (extRegistry.registeredClasses.inverse().get(cls) != null
         || hasGraalvmSerializerClass(cls)
         || shimDispatcher.contains(cls)) {
       return true;
@@ -1696,7 +1718,7 @@ public class ClassResolver extends TypeResolver {
     if (config.requireClassRegistration()) {
       return Functions.isLambda(cls)
           || ReflectionUtils.isJdkProxy(cls)
-          || isRegisteredByIdLocalOrFrozen(cls)
+          || extRegistry.registeredClassIdMap.get(cls) != null
           || shimDispatcher.contains(cls);
     } else {
       return extRegistry.typeChecker.checkType(this, cls.getName());
@@ -1797,7 +1819,7 @@ public class ClassResolver extends TypeResolver {
 
   private TypeInfo buildClassInfo(Class<?> cls) {
     TypeInfo typeInfo;
-    Integer classId = getRegisteredClassIdLocalOrFrozen(cls);
+    Integer classId = extRegistry.registeredClassIdMap.get(cls);
     // Don't create serializer in case the object for class is 
non-serializable,
     // Or class is abstract or interface.
     int typeId;
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/SharedRegistry.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/SharedRegistry.java
index c8152f6db..b7bc39378 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/SharedRegistry.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/SharedRegistry.java
@@ -37,11 +37,19 @@ import org.apache.fory.meta.Encoders;
 import org.apache.fory.meta.MetaString;
 import org.apache.fory.meta.MetaStringEncoder;
 import org.apache.fory.meta.TypeDef;
+import org.apache.fory.serializer.Serializer;
 import org.apache.fory.type.Descriptor;
 import org.apache.fory.type.DescriptorGrouper;
 import org.apache.fory.util.GraalvmSupport;
 
-/** Shared caches reused by multiple equivalent {@link org.apache.fory.Fory} 
instances. */
+/**
+ * Shared caches reused by multiple equivalent {@link org.apache.fory.Fory} 
instances.
+ *
+ * <p>A {@code SharedRegistry} is scoped to one effective Fory config/mode 
family. Equivalent
+ * runtimes may share the same registry, but incompatible runtimes must not. 
In particular, the
+ * registered serializer and registered {@link TypeInfo} caches below assume 
one config scope and
+ * therefore are keyed only by class.
+ */
 @Internal
 public final class SharedRegistry {
   private static final int MAX_CACHED_ENCODED_META_STRINGS = 32768;
@@ -67,6 +75,10 @@ public final class SharedRegistry {
       new ConcurrentHashMap<>();
   final ConcurrentHashMap<EncodedMetaStringKey, EncodedMetaString> 
encodedMetaStringMap =
       new ConcurrentHashMap<>();
+  final ConcurrentIdentityMap<Class<?>, TypeInfo> registeredTypeInfoCache =
+      new ConcurrentIdentityMap<>();
+  final ConcurrentIdentityMap<Class<?>, Serializer<?>> 
registeredSerializerCache =
+      new ConcurrentIdentityMap<>();
   private final Object metaStringCacheLock = new Object();
   volatile IdentityHashMap<Class<?>, Integer> registeredClassIdMap;
   volatile BiMap<String, Class<?>> registeredClasses;
@@ -90,6 +102,29 @@ public final class SharedRegistry {
     return Objects.requireNonNull(registeredClasses);
   }
 
+  Serializer<?> getRegisteredSerializer(Class<?> type) {
+    return registeredSerializerCache.get(type);
+  }
+
+  Serializer<?> cacheRegisteredSerializer(Class<?> type, Serializer<?> 
serializer) {
+    Serializer<?> existing = registeredSerializerCache.putIfAbsent(type, 
serializer);
+    if (existing == null) {
+      return serializer;
+    }
+    if (existing.getClass() != serializer.getClass()) {
+      throw new IllegalArgumentException(
+          String.format(
+              "Conflicting shareable serializer registration for %s. 
Existing=%s, new=%s",
+              type.getName(), existing, serializer));
+    }
+    return existing;
+  }
+
+  TypeInfo cacheRegisteredTypeInfo(Class<?> type, TypeInfo typeInfo) {
+    TypeInfo existing = registeredTypeInfoCache.putIfAbsent(type, typeInfo);
+    return existing == null ? typeInfo : existing;
+  }
+
   EncodedMetaString getPackageEncodedMetaString(String string) {
     return getEncodedMetaString(
         string,
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
index cb2d4d23c..ff7cecb50 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
@@ -944,7 +944,7 @@ public abstract class TypeResolver {
       }
       return localTypeInfo;
     }
-    Integer classId = getRegisteredClassIdLocalOrFrozen(cls);
+    Integer classId = extRegistry.registeredClassIdMap.get(cls);
     int typeId;
     if (classId != null) {
       TypeInfo registeredInfo = classInfoMap.get(cls);
@@ -1071,7 +1071,7 @@ public abstract class TypeResolver {
   final Class<?> loadClass(
       String className, boolean isEnum, int arrayDims, boolean 
deserializeUnknownClass) {
     extRegistry.typeChecker.checkType(this, className);
-    Class<?> cls = getRegisteredClassLocalOrFrozen(className);
+    Class<?> cls = extRegistry.registeredClasses.get(className);
     if (cls != null) {
       return cls;
     }
@@ -1716,42 +1716,6 @@ public abstract class TypeResolver {
     return null;
   }
 
-  /**
-   * Reads a registered class id from the mutable local maps before freeze or 
the shared maps after
-   * {@link #finishRegistration()}.
-   */
-  final Integer getRegisteredClassIdLocalOrFrozen(Class<?> cls) {
-    return extRegistry.registeredClassIdMap.get(cls);
-  }
-
-  /**
-   * Reads a registered class name from the mutable local maps before freeze 
or the shared maps
-   * after {@link #finishRegistration()}.
-   */
-  final String getRegisteredNameLocalOrFrozen(Class<?> cls) {
-    return extRegistry.registeredClasses.inverse().get(cls);
-  }
-
-  /**
-   * Reads a registered class lookup by name from the mutable local maps 
before freeze or the shared
-   * maps after {@link #finishRegistration()}.
-   */
-  final Class<?> getRegisteredClassLocalOrFrozen(String className) {
-    return extRegistry.registeredClasses.get(className);
-  }
-
-  final boolean isRegisteredByIdLocalOrFrozen(Class<?> cls) {
-    return getRegisteredClassIdLocalOrFrozen(cls) != null;
-  }
-
-  final boolean isRegisteredByNameLocalOrFrozen(Class<?> cls) {
-    return getRegisteredNameLocalOrFrozen(cls) != null;
-  }
-
-  final boolean isRegisteredByNameLocalOrFrozen(String className) {
-    return getRegisteredClassLocalOrFrozen(className) != null;
-  }
-
   public GenericType getObjectGenericType() {
     return extRegistry.objectGenericType;
   }
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
index acc5b7b55..3fb39fb6f 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
@@ -86,6 +86,7 @@ import org.apache.fory.serializer.PrimitiveSerializers;
 import org.apache.fory.serializer.SerializationUtils;
 import org.apache.fory.serializer.Serializer;
 import org.apache.fory.serializer.Serializers;
+import org.apache.fory.serializer.Shareable;
 import org.apache.fory.serializer.TimeSerializers;
 import org.apache.fory.serializer.UnionSerializer;
 import org.apache.fory.serializer.UnknownClass;
@@ -129,8 +130,6 @@ public class XtypeResolver extends TypeResolver {
   // Every deserialization for unregistered class will query it, performance 
is important.
   private final ObjectMap<TypeNameBytes, TypeInfo> 
compositeClassNameBytes2TypeInfo =
       new ObjectMap<>(16, loadFactor);
-  private final ObjectMap<String, TypeInfo> qualifiedType2TypeInfo =
-      new ObjectMap<>(16, loadFactor);
   // typeDefMap is inherited from TypeResolver
   private final boolean shareMeta;
   private int xtypeIdGenerator = 64;
@@ -326,8 +325,12 @@ public class XtypeResolver extends TypeResolver {
       }
     }
     typeInfo.setSerializer(this, serializer);
-    qualifiedType2TypeInfo.put(qualifiedName, typeInfo);
     extRegistry.registeredClasses.put(qualifiedName, type);
+    if (typeInfo.typeName != null) {
+      TypeNameBytes typeNameBytes =
+          new TypeNameBytes(typeInfo.namespace.hash, typeInfo.typeName.hash);
+      compositeClassNameBytes2TypeInfo.put(typeNameBytes, typeInfo);
+    }
     registerGraalvmClass(type);
     updateTypeInfo(type, typeInfo);
   }
@@ -451,6 +454,11 @@ public class XtypeResolver extends TypeResolver {
     if 
(!serializer.getClass().getPackage().getName().startsWith("org.apache.fory")) {
       SerializationUtils.validate(type, serializer.getClass());
     }
+    boolean localOverride = typeInfo.serializer != null;
+    boolean shouldShare = serializer instanceof Shareable && !localOverride;
+    if (shouldShare) {
+      serializer = sharedRegistry.cacheRegisteredSerializer(type, serializer);
+    }
     int oldTypeId = typeInfo.typeId;
     int foryId = oldTypeId;
 
@@ -461,10 +469,11 @@ public class XtypeResolver extends TypeResolver {
     }
     typeInfo = typeInfo.copy(foryId);
     typeInfo.setSerializer(this, serializer);
+    if (shouldShare) {
+      typeInfo = sharedRegistry.cacheRegisteredTypeInfo(type, typeInfo);
+    }
     updateTypeInfo(type, typeInfo);
     if (typeInfo.typeName != null) {
-      String qualifiedName = qualifiedName(typeInfo.decodeNamespace(), 
typeInfo.decodeTypeName());
-      qualifiedType2TypeInfo.put(qualifiedName, typeInfo);
       TypeNameBytes typeNameBytes =
           new TypeNameBytes(typeInfo.namespace.hash, typeInfo.typeName.hash);
       compositeClassNameBytes2TypeInfo.put(typeNameBytes, typeInfo);
@@ -482,14 +491,22 @@ public class XtypeResolver extends TypeResolver {
       return;
     }
     TypeInfo typeInfo = classInfoMap.get(type);
-    if (typeInfo != null) {
-      if (typeInfo.serializer == null) {
-        typeInfo.setSerializer(this, serializer);
-      }
+    if (typeInfo != null && typeInfo.serializer != null) {
       return;
     }
-    int typeId = determineTypeIdForClass(type);
-    typeInfo = newTypeInfo(type, serializer, typeId);
+    boolean shareable = serializer instanceof Shareable;
+    if (shareable) {
+      serializer = sharedRegistry.cacheRegisteredSerializer(type, serializer);
+    }
+    if (typeInfo != null) {
+      typeInfo.setSerializer(this, serializer);
+    } else {
+      int typeId = determineTypeIdForClass(type);
+      typeInfo = newTypeInfo(type, serializer, typeId);
+    }
+    if (shareable) {
+      typeInfo = sharedRegistry.cacheRegisteredTypeInfo(type, typeInfo);
+    }
     classInfoMap.put(type, typeInfo);
   }
 
@@ -699,8 +716,10 @@ public class XtypeResolver extends TypeResolver {
   }
 
   public TypeInfo getUserTypeInfo(String namespace, String typeName) {
-    String name = qualifiedName(namespace, typeName);
-    return qualifiedType2TypeInfo.get(name);
+    EncodedMetaString namespaceBytes = 
sharedRegistry.getPackageEncodedMetaString(namespace);
+    EncodedMetaString typeNameBytes = 
sharedRegistry.getTypeNameEncodedMetaString(typeName);
+    return compositeClassNameBytes2TypeInfo.get(
+        new TypeNameBytes(namespaceBytes.hash, typeNameBytes.hash));
   }
 
   public TypeInfo getUserTypeInfo(int userTypeId) {
@@ -1188,39 +1207,45 @@ public class XtypeResolver extends TypeResolver {
     String namespace = packageBytes.decode(PACKAGE_DECODER);
     String typeName = simpleClassNameBytes.decode(TYPE_NAME_DECODER);
     String qualifiedName = qualifiedName(namespace, typeName);
-    TypeInfo typeInfo = qualifiedType2TypeInfo.get(qualifiedName);
-    if (typeInfo == null) {
-      String msg = String.format("Class %s not registered", qualifiedName);
-      Class<?> type = null;
-      if (config.deserializeUnknownClass()) {
-        LOG.warn(msg);
-        switch (typeId) {
-          case Types.NAMED_ENUM:
-          case Types.NAMED_STRUCT:
-          case Types.NAMED_COMPATIBLE_STRUCT:
-            type =
-                UnknownClass.getUnknowClass(
-                    qualifiedName, isEnum(typeId), 0, 
config.isMetaShareEnabled());
-            break;
-          case Types.NAMED_EXT:
-            throw new SerializerUnregisteredException(qualifiedName);
-          default:
-            break;
-        }
-      } else {
-        throw new ClassUnregisteredException(qualifiedName);
+    Class<?> registeredType = extRegistry.registeredClasses.get(qualifiedName);
+    if (registeredType != null) {
+      TypeInfo typeInfo = classInfoMap.get(registeredType);
+      if (typeInfo == null) {
+        typeInfo = getTypeInfo(registeredType);
       }
-      typeInfo =
-          new TypeInfo(
-              type,
-              packageBytes,
-              simpleClassNameBytes,
-              null,
-              NOT_SUPPORT_XLANG,
-              INVALID_USER_TYPE_ID);
-      if 
(UnknownClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(type))) {
-        typeInfo.serializer = UnknownClassSerializers.getSerializer(this, 
qualifiedName, type);
+      compositeClassNameBytes2TypeInfo.put(typeNameBytes, typeInfo);
+      return typeInfo;
+    }
+    String msg = String.format("Class %s not registered", qualifiedName);
+    Class<?> type = null;
+    if (config.deserializeUnknownClass()) {
+      LOG.warn(msg);
+      switch (typeId) {
+        case Types.NAMED_ENUM:
+        case Types.NAMED_STRUCT:
+        case Types.NAMED_COMPATIBLE_STRUCT:
+          type =
+              UnknownClass.getUnknowClass(
+                  qualifiedName, isEnum(typeId), 0, 
config.isMetaShareEnabled());
+          break;
+        case Types.NAMED_EXT:
+          throw new SerializerUnregisteredException(qualifiedName);
+        default:
+          break;
       }
+    } else {
+      throw new ClassUnregisteredException(qualifiedName);
+    }
+    TypeInfo typeInfo =
+        new TypeInfo(
+            type,
+            packageBytes,
+            simpleClassNameBytes,
+            null,
+            NOT_SUPPORT_XLANG,
+            INVALID_USER_TYPE_ID);
+    if 
(UnknownClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(type))) {
+      typeInfo.serializer = UnknownClassSerializers.getSerializer(this, 
qualifiedName, type);
     }
     compositeClassNameBytes2TypeInfo.put(typeNameBytes, typeInfo);
     return typeInfo;
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java
index 0f7c6e80c..935047137 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/ArraySerializers.java
@@ -252,18 +252,14 @@ public class ArraySerializers {
 
   // Implement all read/write methods in subclasses to avoid
   // virtual method call cost.
-  public abstract static class PrimitiveArraySerializer<T> extends 
Serializer<T> {
+  public abstract static class PrimitiveArraySerializer<T> extends 
Serializer<T>
+      implements Shareable {
     protected final Config config;
 
     public PrimitiveArraySerializer(TypeResolver typeResolver, Class<T> cls) {
       super(typeResolver.getConfig(), cls);
       this.config = typeResolver.getConfig();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static final class BooleanArraySerializer extends 
PrimitiveArraySerializer<boolean[]> {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/BufferSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/BufferSerializers.java
index 1783b0cf4..d5d52a4ce 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/BufferSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/BufferSerializers.java
@@ -35,7 +35,8 @@ public class BufferSerializers {
    * doesn't implement {@link java.io.Serializable}, it's ok to only serialize 
data. Also Note that
    * a direct buffer may be returned if the serialized buffer is a heap buffer.
    */
-  public static final class ByteBufferSerializer extends 
Serializer<ByteBuffer> {
+  public static final class ByteBufferSerializer extends Serializer<ByteBuffer>
+      implements Shareable {
 
     public ByteBufferSerializer(TypeResolver typeResolver, Class<ByteBuffer> 
cls) {
       super(typeResolver.getConfig(), cls);
@@ -65,11 +66,6 @@ public class BufferSerializers {
           isBigEndian == (byte) 1 ? ByteOrder.BIG_ENDIAN : 
ByteOrder.LITTLE_ENDIAN);
       return originalBuffer;
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   // TODO(chaokunyang) add support for MemoryBuffer serialization.
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java
index 23609e4f7..e88681be5 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java
@@ -28,7 +28,7 @@ import org.apache.fory.context.WriteContext;
 import org.apache.fory.util.Preconditions;
 
 @SuppressWarnings("rawtypes")
-public class EnumSerializer extends ImmutableSerializer<Enum> {
+public class EnumSerializer extends ImmutableSerializer<Enum> implements 
Shareable {
   private final Config config;
   private final Enum[] enumConstants;
   private final Map<String, Enum> stringToEnum;
@@ -110,9 +110,4 @@ public class EnumSerializer extends 
ImmutableSerializer<Enum> {
             String.format("Enum string %s not in %s", value, 
Arrays.toString(enumConstants)));
     }
   }
-
-  @Override
-  public boolean shareable() {
-    return config.forVirtualThread();
-  }
 }
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java
index f8f585685..d88fa4690 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/Float16Serializer.java
@@ -24,7 +24,7 @@ import org.apache.fory.context.ReadContext;
 import org.apache.fory.context.WriteContext;
 import org.apache.fory.type.Float16;
 
-public final class Float16Serializer extends ImmutableSerializer<Float16> {
+public final class Float16Serializer extends ImmutableSerializer<Float16> 
implements Shareable {
 
   public Float16Serializer(Config config) {
     super(config, Float16.class);
@@ -39,9 +39,4 @@ public final class Float16Serializer extends 
ImmutableSerializer<Float16> {
   public Float16 read(ReadContext readContext) {
     return Float16.fromBits(readContext.getBuffer().readInt16());
   }
-
-  @Override
-  public boolean shareable() {
-    return true;
-  }
 }
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/LocaleSerializer.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/LocaleSerializer.java
index 7f7f3af5d..00ae9b088 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/LocaleSerializer.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/LocaleSerializer.java
@@ -28,7 +28,7 @@ import org.apache.fory.context.ReadContext;
 import org.apache.fory.context.WriteContext;
 
 /** Local serializer for {@link Locale}. */
-public final class LocaleSerializer extends ImmutableSerializer<Locale> {
+public final class LocaleSerializer extends ImmutableSerializer<Locale> 
implements Shareable {
   // Using `new HashMap<>` to ensure thread safety by java constructor 
semantics.
   private static final Map<Tuple3<String, String, String>, Locale> 
LOCALE_CACHE =
       new HashMap<>(createCacheMap());
@@ -103,9 +103,4 @@ public final class LocaleSerializer extends 
ImmutableSerializer<Locale> {
         && locale.getCountry().equals(country)
         && locale.getVariant().equals(variant));
   }
-
-  @Override
-  public boolean shareable() {
-    return true;
-  }
 }
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/OptionalSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/OptionalSerializers.java
index abb9dfe00..8625c1cf7 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/OptionalSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/OptionalSerializers.java
@@ -60,7 +60,8 @@ public final class OptionalSerializers {
     }
   }
 
-  public static final class OptionalIntSerializer extends 
ImmutableSerializer<OptionalInt> {
+  public static final class OptionalIntSerializer extends 
ImmutableSerializer<OptionalInt>
+      implements Shareable {
     public OptionalIntSerializer(Config config) {
       super(config, OptionalInt.class);
     }
@@ -82,14 +83,10 @@ public final class OptionalSerializers {
         return OptionalInt.empty();
       }
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class OptionalLongSerializer extends 
ImmutableSerializer<OptionalLong> {
+  public static final class OptionalLongSerializer extends 
ImmutableSerializer<OptionalLong>
+      implements Shareable {
     public OptionalLongSerializer(Config config) {
       super(config, OptionalLong.class);
     }
@@ -111,14 +108,10 @@ public final class OptionalSerializers {
         return OptionalLong.empty();
       }
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class OptionalDoubleSerializer extends 
ImmutableSerializer<OptionalDouble> {
+  public static final class OptionalDoubleSerializer extends 
ImmutableSerializer<OptionalDouble>
+      implements Shareable {
     public OptionalDoubleSerializer(Config config) {
       super(config, OptionalDouble.class);
     }
@@ -140,11 +133,6 @@ public final class OptionalSerializers {
         return OptionalDouble.empty();
       }
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static void registerDefaultSerializers(TypeResolver resolver) {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java
index 1b90c9478..8537456e2 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/PrimitiveSerializers.java
@@ -36,7 +36,8 @@ import org.apache.fory.util.Preconditions;
 /** Serializers for java primitive types. */
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class PrimitiveSerializers {
-  public static final class BooleanSerializer extends 
ImmutableSerializer<Boolean> {
+  public static final class BooleanSerializer extends 
ImmutableSerializer<Boolean>
+      implements Shareable {
     public BooleanSerializer(Config config, Class<?> cls) {
       super(config, (Class) cls, false);
     }
@@ -50,14 +51,9 @@ public class PrimitiveSerializers {
     public Boolean read(ReadContext readContext) {
       return readContext.getBuffer().readBoolean();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class ByteSerializer extends ImmutableSerializer<Byte> {
+  public static final class ByteSerializer extends ImmutableSerializer<Byte> 
implements Shareable {
     public ByteSerializer(Config config, Class<?> cls) {
       super(config, (Class) cls, false);
     }
@@ -71,14 +67,10 @@ public class PrimitiveSerializers {
     public Byte read(ReadContext readContext) {
       return readContext.getBuffer().readByte();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class Uint8Serializer extends 
ImmutableSerializer<Integer> {
+  public static final class Uint8Serializer extends 
ImmutableSerializer<Integer>
+      implements Shareable {
     public Uint8Serializer(Config config) {
       super(config, Integer.class);
     }
@@ -94,14 +86,10 @@ public class PrimitiveSerializers {
       int b = readContext.getBuffer().readByte();
       return b >>> 24;
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class Uint16Serializer extends 
ImmutableSerializer<Integer> {
+  public static final class Uint16Serializer extends 
ImmutableSerializer<Integer>
+      implements Shareable {
     public Uint16Serializer(Config config) {
       super(config, Integer.class);
     }
@@ -117,14 +105,10 @@ public class PrimitiveSerializers {
       int b = readContext.getBuffer().readByte();
       return b >>> 16;
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class CharSerializer extends 
ImmutableSerializer<Character> {
+  public static final class CharSerializer extends 
ImmutableSerializer<Character>
+      implements Shareable {
     public CharSerializer(Config config, Class<?> cls) {
       super(config, (Class) cls, false);
     }
@@ -138,14 +122,10 @@ public class PrimitiveSerializers {
     public Character read(ReadContext readContext) {
       return readContext.getBuffer().readChar();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class ShortSerializer extends ImmutableSerializer<Short> 
{
+  public static final class ShortSerializer extends ImmutableSerializer<Short>
+      implements Shareable {
     public ShortSerializer(Config config, Class<?> cls) {
       super(config, (Class) cls, false);
     }
@@ -159,14 +139,10 @@ public class PrimitiveSerializers {
     public Short read(ReadContext readContext) {
       return readContext.getBuffer().readInt16();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class IntSerializer extends ImmutableSerializer<Integer> 
{
+  public static final class IntSerializer extends ImmutableSerializer<Integer>
+      implements Shareable {
     private final boolean compressNumber;
 
     public IntSerializer(Config config, Class<?> cls) {
@@ -191,14 +167,9 @@ public class PrimitiveSerializers {
       }
       return readContext.getBuffer().readInt32();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class VarUint32Serializer extends Serializer<Integer> {
+  public static final class VarUint32Serializer extends Serializer<Integer> 
implements Shareable {
     public VarUint32Serializer(Config config) {
       super(config, Integer.class);
     }
@@ -213,14 +184,9 @@ public class PrimitiveSerializers {
     public Integer read(ReadContext readContext) {
       return readContext.getBuffer().readVarUint32();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class LongSerializer extends ImmutableSerializer<Long> {
+  public static final class LongSerializer extends ImmutableSerializer<Long> 
implements Shareable {
     private final LongEncoding longEncoding;
 
     public LongSerializer(Config config, Class<?> cls) {
@@ -289,14 +255,9 @@ public class PrimitiveSerializers {
           throw new UnsupportedOperationException("Unsupported long encoding " 
+ longEncoding);
       }
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class VarUint64Serializer extends Serializer<Long> {
+  public static final class VarUint64Serializer extends Serializer<Long> 
implements Shareable {
     public VarUint64Serializer(Config config) {
       super(config, Long.class);
     }
@@ -311,14 +272,10 @@ public class PrimitiveSerializers {
     public Long read(ReadContext readContext) {
       return readContext.getBuffer().readVarUint64();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class FloatSerializer extends ImmutableSerializer<Float> 
{
+  public static final class FloatSerializer extends ImmutableSerializer<Float>
+      implements Shareable {
     public FloatSerializer(Config config, Class<?> cls) {
       super(config, (Class) cls, false);
     }
@@ -332,14 +289,10 @@ public class PrimitiveSerializers {
     public Float read(ReadContext readContext) {
       return readContext.getBuffer().readFloat32();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class DoubleSerializer extends 
ImmutableSerializer<Double> {
+  public static final class DoubleSerializer extends 
ImmutableSerializer<Double>
+      implements Shareable {
     public DoubleSerializer(Config config, Class<?> cls) {
       super(config, (Class) cls, false);
     }
@@ -353,14 +306,10 @@ public class PrimitiveSerializers {
     public Double read(ReadContext readContext) {
       return readContext.getBuffer().readFloat64();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class Float16Serializer extends 
ImmutableSerializer<Float16> {
+  public static final class Float16Serializer extends 
ImmutableSerializer<Float16>
+      implements Shareable {
     public Float16Serializer(Config config, Class<?> cls) {
       super(config, (Class) cls, false);
     }
@@ -374,11 +323,6 @@ public class PrimitiveSerializers {
     public Float16 read(ReadContext readContext) {
       return Float16.fromBits(readContext.getBuffer().readInt16());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static void registerDefaultSerializers(TypeResolver resolver) {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializer.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializer.java
index 1ed7bce46..fdd318bed 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializer.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializer.java
@@ -38,10 +38,17 @@ import org.apache.fory.type.TypeUtils;
  * <p>This type stays an abstract class instead of an interface so hot-path 
flag checks such as
  * {@link #needToWriteRef()} avoid an extra virtual dispatch.
  *
- * <p>Unless a subclass overrides {@link #shareable()}, instances are 
runtime-local and must not be
- * shared across equivalent runtimes or concurrent operations.
+ * <h3>Sharing across runtimes</h3>
+ *
+ * <p>By default, serializer instances are <b>runtime-local</b> and must not 
be shared across
+ * equivalent Fory runtimes or concurrent operations. A serializer that is 
safe to share must
+ * implement the {@link Shareable} marker interface. Consumers can check 
shareability via {@code
+ * serializer instanceof Shareable}.
+ *
+ * <p>See {@link Shareable} for the full contract and guidelines on when to 
implement it.
  *
  * @param <T> value type handled by this serializer
+ * @see Shareable
  */
 @SuppressWarnings("unchecked")
 public abstract class Serializer<T> {
@@ -199,33 +206,4 @@ public abstract class Serializer<T> {
   public boolean isImmutable() {
     return immutable;
   }
-
-  /**
-   * Returns whether this serializer instance may be shared across equivalent 
runtimes.
-   *
-   * <p>The default is {@code false}, which means the serializer instance is 
runtime-local and must
-   * only be used by the runtime that created it.
-   *
-   * <p>Returning {@code true} is a stronger contract than ordinary thread 
safety. A shareable
-   * serializer must satisfy all the following:
-   *
-   * <ul>
-   *   <li>It is safe for concurrent use by multiple threads.
-   *   <li>It does not retain operation-local mutable state such as active 
{@link WriteContext},
-   *       {@link ReadContext}, {@link CopyContext}, or temporary {@link 
MemoryBuffer} references.
-   *   <li>It does not retain runtime-local mutable state whose behavior 
depends on a specific
-   *       runtime instance, such as mutable resolver state tied to a 
particular class-loading or
-   *       registration session.
-   *   <li>Any retained fields are immutable, effectively immutable, or 
otherwise concurrency-safe
-   *       and independent of the calling runtime.
-   * </ul>
-   *
-   * <p>Override this only in concrete leaf serializers. Intermediate base 
classes should keep the
-   * default to avoid accidentally widening sharing for subclasses that carry 
mutable state.
-   *
-   * <p>If in doubt, keep the default {@code false}.
-   */
-  public boolean shareable() {
-    return false;
-  }
 }
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
index 052353b8b..601523962 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
@@ -407,7 +407,8 @@ public class Serializers {
     }
   }
 
-  public static final class BigDecimalSerializer extends 
ImmutableSerializer<BigDecimal> {
+  public static final class BigDecimalSerializer extends 
ImmutableSerializer<BigDecimal>
+      implements Shareable {
     public BigDecimalSerializer(Config config) {
       super(config, BigDecimal.class);
     }
@@ -432,14 +433,10 @@ public class Serializers {
       final BigInteger bigInteger = new BigInteger(bytes);
       return new BigDecimal(bigInteger, scale, new MathContext(precision));
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class BigIntegerSerializer extends 
ImmutableSerializer<BigInteger> {
+  public static final class BigIntegerSerializer extends 
ImmutableSerializer<BigInteger>
+      implements Shareable {
     public BigIntegerSerializer(Config config) {
       super(config, BigInteger.class);
     }
@@ -459,14 +456,10 @@ public class Serializers {
       byte[] bytes = buffer.readBytes(len);
       return new BigInteger(bytes);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class AtomicBooleanSerializer extends 
Serializer<AtomicBoolean> {
+  public static final class AtomicBooleanSerializer extends 
Serializer<AtomicBoolean>
+      implements Shareable {
 
     public AtomicBooleanSerializer(Config config) {
       super(config, AtomicBoolean.class);
@@ -486,14 +479,10 @@ public class Serializers {
     public AtomicBoolean read(ReadContext readContext) {
       return new AtomicBoolean(readContext.getBuffer().readBoolean());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class AtomicIntegerSerializer extends 
Serializer<AtomicInteger> {
+  public static final class AtomicIntegerSerializer extends 
Serializer<AtomicInteger>
+      implements Shareable {
 
     public AtomicIntegerSerializer(Config config) {
       super(config, AtomicInteger.class);
@@ -513,14 +502,10 @@ public class Serializers {
     public AtomicInteger read(ReadContext readContext) {
       return new AtomicInteger(readContext.getBuffer().readInt32());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class AtomicLongSerializer extends 
Serializer<AtomicLong> {
+  public static final class AtomicLongSerializer extends Serializer<AtomicLong>
+      implements Shareable {
 
     public AtomicLongSerializer(Config config) {
       super(config, AtomicLong.class);
@@ -540,14 +525,10 @@ public class Serializers {
     public AtomicLong read(ReadContext readContext) {
       return new AtomicLong(readContext.getBuffer().readInt64());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class AtomicReferenceSerializer extends 
Serializer<AtomicReference> {
+  public static final class AtomicReferenceSerializer extends 
Serializer<AtomicReference>
+      implements Shareable {
 
     public AtomicReferenceSerializer(Config config) {
       super(config, AtomicReference.class);
@@ -567,14 +548,10 @@ public class Serializers {
     public AtomicReference read(ReadContext readContext) {
       return new AtomicReference(readContext.readRef());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class CurrencySerializer extends 
ImmutableSerializer<Currency> {
+  public static final class CurrencySerializer extends 
ImmutableSerializer<Currency>
+      implements Shareable {
     public CurrencySerializer(Config config) {
       super(config, Currency.class);
     }
@@ -588,15 +565,11 @@ public class Serializers {
     public Currency read(ReadContext readContext) {
       return Currency.getInstance(readContext.readString());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   /** Serializer for {@link Charset}. */
-  public static final class CharsetSerializer<T extends Charset> extends 
ImmutableSerializer<T> {
+  public static final class CharsetSerializer<T extends Charset> extends 
ImmutableSerializer<T>
+      implements Shareable {
     public CharsetSerializer(Config config, Class<T> type) {
       super(config, type);
     }
@@ -608,14 +581,10 @@ public class Serializers {
     public T read(ReadContext readContext) {
       return (T) Charset.forName(readContext.readString());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class URISerializer extends 
ImmutableSerializer<java.net.URI> {
+  public static final class URISerializer extends 
ImmutableSerializer<java.net.URI>
+      implements Shareable {
 
     public URISerializer(Config config) {
       super(config, URI.class);
@@ -630,14 +599,10 @@ public class Serializers {
     public URI read(ReadContext readContext) {
       return URI.create(readContext.readString());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class RegexSerializer extends 
ImmutableSerializer<Pattern> {
+  public static final class RegexSerializer extends 
ImmutableSerializer<Pattern>
+      implements Shareable {
     public RegexSerializer(Config config) {
       super(config, Pattern.class);
     }
@@ -656,14 +621,9 @@ public class Serializers {
       int flags = buffer.readInt32();
       return Pattern.compile(regex, flags);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class UUIDSerializer extends ImmutableSerializer<UUID> {
+  public static final class UUIDSerializer extends ImmutableSerializer<UUID> 
implements Shareable {
 
     public UUIDSerializer(Config config) {
       super(config, UUID.class);
@@ -681,14 +641,10 @@ public class Serializers {
       MemoryBuffer buffer = readContext.getBuffer();
       return new UUID(buffer.readInt64(), buffer.readInt64());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class ClassSerializer extends ImmutableSerializer<Class> 
{
+  public static final class ClassSerializer extends ImmutableSerializer<Class>
+      implements Shareable {
     public ClassSerializer(Config config) {
       super(config, Class.class);
     }
@@ -702,11 +658,6 @@ public class Serializers {
     public Class read(ReadContext readContext) {
       return ((ClassResolver) 
readContext.getTypeResolver()).readClassInternal(readContext);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   /**
@@ -716,7 +667,8 @@ public class Serializers {
    * serializable or class registration checks.
    */
   // Use a separate serializer to avoid codegen for empty object.
-  public static final class EmptyObjectSerializer extends 
ImmutableSerializer<Object> {
+  public static final class EmptyObjectSerializer extends 
ImmutableSerializer<Object>
+      implements Shareable {
 
     public EmptyObjectSerializer(Config config) {
       super(config, Object.class);
@@ -729,11 +681,6 @@ public class Serializers {
     public Object read(ReadContext readContext) {
       return new Object();
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static void registerDefaultSerializers(TypeResolver resolver) {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/Shareable.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/Shareable.java
new file mode 100644
index 000000000..eb5cfdfe4
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Shareable.java
@@ -0,0 +1,92 @@
+/*
+ * 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.CopyContext;
+import org.apache.fory.context.ReadContext;
+import org.apache.fory.context.WriteContext;
+import org.apache.fory.memory.MemoryBuffer;
+
+/**
+ * Marker interface for {@link Serializer} instances that can be shared across 
equivalent Fory
+ * runtimes and concurrent operations.
+ *
+ * <p>Implementing this interface is a stronger contract than ordinary thread 
safety. A shareable
+ * serializer must satisfy <b>all</b> of the following conditions:
+ *
+ * <ul>
+ *   <li><b>Thread-safe:</b> the serializer is safe for concurrent use by 
multiple threads with no
+ *       external synchronization.
+ *   <li><b>No operation-local mutable state:</b> the serializer does not 
retain active {@link
+ *       WriteContext}, {@link ReadContext}, {@link CopyContext}, or temporary 
{@link MemoryBuffer}
+ *       references across calls. All operation state must be passed through 
the context parameters.
+ *   <li><b>No runtime-local mutable state:</b> the serializer does not depend 
on mutable state tied
+ *       to a specific runtime instance, such as mutable resolver state from a 
particular
+ *       class-loading or registration session.
+ *   <li><b>Immutable or concurrency-safe retained fields:</b> any fields 
retained by the serializer
+ *       are immutable, effectively immutable, or otherwise concurrency-safe 
and independent of the
+ *       calling runtime.
+ * </ul>
+ *
+ * <h3>When to implement</h3>
+ *
+ * <ul>
+ *   <li>Serializers that only depend on {@link org.apache.fory.config.Config} 
(immutable
+ *       configuration) are typically shareable.
+ *   <li>Serializers for immutable value types (primitives, time types, enums, 
UUIDs, etc.) are
+ *       almost always shareable.
+ *   <li>Serializers whose fields are derived entirely from the target type 
and configuration at
+ *       construction time are shareable.
+ * </ul>
+ *
+ * <h3>When NOT to implement</h3>
+ *
+ * <ul>
+ *   <li>Serializers that retain a {@link 
org.apache.fory.resolver.TypeResolver} reference are
+ *       usually <b>not</b> shareable because the resolver carries 
runtime-local mutable state.
+ *   <li>Serializers that cache intermediate results per-operation (scratch 
buffers, temporary
+ *       collections, etc.) in instance fields are not shareable.
+ *   <li>When in doubt, do not implement this interface. The default 
assumption is that a serializer
+ *       is runtime-local.
+ * </ul>
+ *
+ * <h3>Usage</h3>
+ *
+ * <p>Implement this interface on concrete leaf serializer classes only. Do 
not implement it on
+ * intermediate abstract base classes to avoid accidentally widening sharing 
for subclasses that
+ * carry mutable state.
+ *
+ * <pre>
+ * public final class FooSerializer extends Serializer&lt;Foo&gt; implements 
Shareable {
+ *   public FooSerializer(Config config) {
+ *     super(config, Foo.class);
+ *   }
+ *
+ *   public void write(WriteContext ctx, Foo value) { ... }
+ *
+ *   public Foo read(ReadContext ctx) { ... }
+ * }
+ * </pre>
+ *
+ * <p>Consumers can check shareability via {@code serializer instanceof 
Shareable}.
+ *
+ * @see Serializer
+ */
+public interface Shareable {}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java
index 32039a1ed..95ce87e0a 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java
@@ -204,7 +204,8 @@ public class TimeSerializers {
     }
   }
 
-  public static final class LocalDateSerializer extends 
ImmutableTimeSerializer<LocalDate> {
+  public static final class LocalDateSerializer extends 
ImmutableTimeSerializer<LocalDate>
+      implements Shareable {
     public LocalDateSerializer(Config config) {
       super(config, LocalDate.class);
     }
@@ -245,14 +246,10 @@ public class TimeSerializers {
       int dayOfMonth = buffer.readByte();
       return LocalDate.of(year, month, dayOfMonth);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class InstantSerializer extends 
ImmutableTimeSerializer<Instant> {
+  public static final class InstantSerializer extends 
ImmutableTimeSerializer<Instant>
+      implements Shareable {
     public InstantSerializer(Config config) {
       super(config, Instant.class);
     }
@@ -275,14 +272,10 @@ public class TimeSerializers {
       int nanos = buffer.readInt32();
       return Instant.ofEpochSecond(seconds, nanos);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class DurationSerializer extends 
ImmutableTimeSerializer<Duration> {
+  public static class DurationSerializer extends 
ImmutableTimeSerializer<Duration>
+      implements Shareable {
     public DurationSerializer(Config config) {
       super(config, Duration.class);
     }
@@ -305,14 +298,10 @@ public class TimeSerializers {
       int nanos = buffer.readInt32();
       return Duration.ofSeconds(seconds, nanos);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class LocalDateTimeSerializer extends 
ImmutableTimeSerializer<LocalDateTime> {
+  public static class LocalDateTimeSerializer extends 
ImmutableTimeSerializer<LocalDateTime>
+      implements Shareable {
     public LocalDateTimeSerializer(Config config) {
       super(config, LocalDateTime.class);
     }
@@ -335,14 +324,10 @@ public class TimeSerializers {
       LocalTime time = LocalTimeSerializer.readLocalTime(buffer);
       return LocalDateTime.of(date, time);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class LocalTimeSerializer extends 
ImmutableTimeSerializer<LocalTime> {
+  public static class LocalTimeSerializer extends 
ImmutableTimeSerializer<LocalTime>
+      implements Shareable {
     public LocalTimeSerializer(Config config) {
       super(config, LocalTime.class);
     }
@@ -407,11 +392,6 @@ public class TimeSerializers {
       }
       return LocalTime.of(hour, minute, second, nano);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static class TimeZoneSerializer extends TimeSerializer<TimeZone> {
@@ -496,7 +476,8 @@ public class TimeSerializers {
     }
   }
 
-  public static class ZoneIdSerializer extends ImmutableTimeSerializer<ZoneId> 
{
+  public static class ZoneIdSerializer extends ImmutableTimeSerializer<ZoneId>
+      implements Shareable {
     public ZoneIdSerializer(Config config, Class<ZoneId> type) {
       super(config, type);
     }
@@ -514,14 +495,10 @@ public class TimeSerializers {
     public ZoneId read(ReadContext readContext) {
       return ZoneId.of(readContext.readString());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class ZoneOffsetSerializer extends 
ImmutableTimeSerializer<ZoneOffset> {
+  public static class ZoneOffsetSerializer extends 
ImmutableTimeSerializer<ZoneOffset>
+      implements Shareable {
 
     // cached zone offsets for the single byte representation, using this 
overrides the JDK zone
     // offset caching which uses a concurrent hash map for zone offsets that 
causes a noticeable
@@ -569,14 +546,10 @@ public class TimeSerializers {
       }
       return COMPRESSED_ZONE_OFFSETS[offsetByte + 72];
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class ZonedDateTimeSerializer extends 
ImmutableTimeSerializer<ZonedDateTime> {
+  public static class ZonedDateTimeSerializer extends 
ImmutableTimeSerializer<ZonedDateTime>
+      implements Shareable {
 
     public ZonedDateTimeSerializer(Config config) {
       super(config, ZonedDateTime.class);
@@ -600,14 +573,9 @@ public class TimeSerializers {
       ZoneId zone = ZoneId.of(readContext.readString());
       return ZonedDateTime.of(date, time, zone);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class YearSerializer extends ImmutableTimeSerializer<Year> {
+  public static class YearSerializer extends ImmutableTimeSerializer<Year> 
implements Shareable {
     public YearSerializer(Config config) {
       super(config, Year.class);
     }
@@ -627,14 +595,10 @@ public class TimeSerializers {
       MemoryBuffer buffer = readContext.getBuffer();
       return Year.of(buffer.readInt32());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class YearMonthSerializer extends 
ImmutableTimeSerializer<YearMonth> {
+  public static class YearMonthSerializer extends 
ImmutableTimeSerializer<YearMonth>
+      implements Shareable {
     public YearMonthSerializer(Config config) {
       super(config, YearMonth.class);
     }
@@ -657,14 +621,10 @@ public class TimeSerializers {
       byte month = buffer.readByte();
       return YearMonth.of(year, month);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class MonthDaySerializer extends 
ImmutableTimeSerializer<MonthDay> {
+  public static class MonthDaySerializer extends 
ImmutableTimeSerializer<MonthDay>
+      implements Shareable {
     public MonthDaySerializer(Config config) {
       super(config, MonthDay.class);
     }
@@ -685,14 +645,10 @@ public class TimeSerializers {
       byte day = buffer.readByte();
       return MonthDay.of(month, day);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class PeriodSerializer extends ImmutableTimeSerializer<Period> 
{
+  public static class PeriodSerializer extends ImmutableTimeSerializer<Period>
+      implements Shareable {
     public PeriodSerializer(Config config) {
       super(config, Period.class);
     }
@@ -715,14 +671,10 @@ public class TimeSerializers {
       int days = buffer.readInt32();
       return Period.of(years, months, days);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class OffsetTimeSerializer extends 
ImmutableTimeSerializer<OffsetTime> {
+  public static class OffsetTimeSerializer extends 
ImmutableTimeSerializer<OffsetTime>
+      implements Shareable {
     public OffsetTimeSerializer(Config config) {
       super(config, OffsetTime.class);
     }
@@ -743,14 +695,10 @@ public class TimeSerializers {
       ZoneOffset offset = ZoneOffsetSerializer.readZoneOffset(buffer);
       return OffsetTime.of(time, offset);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class OffsetDateTimeSerializer extends 
ImmutableTimeSerializer<OffsetDateTime> {
+  public static class OffsetDateTimeSerializer extends 
ImmutableTimeSerializer<OffsetDateTime>
+      implements Shareable {
     public OffsetDateTimeSerializer(Config config) {
       super(config, OffsetDateTime.class);
     }
@@ -773,11 +721,6 @@ public class TimeSerializers {
       ZoneOffset offset = ZoneOffsetSerializer.readZoneOffset(buffer);
       return OffsetDateTime.of(date, time, offset);
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static void registerDefaultSerializers(TypeResolver resolver) {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java
index e4804d35b..08ce7dc64 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java
@@ -254,7 +254,8 @@ public final class UnknownClassSerializers {
     }
   }
 
-  public static final class UnknownEnumSerializer extends 
ImmutableSerializer<UnknownEnum> {
+  public static final class UnknownEnumSerializer extends 
ImmutableSerializer<UnknownEnum>
+      implements Shareable {
     private final Config config;
     private final UnknownEnum[] enumConstants;
 
@@ -286,11 +287,6 @@ public final class UnknownClassSerializers {
       }
       return enumConstants[ordinal];
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static Serializer getSerializer(
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/UnsignedSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnsignedSerializers.java
index 5f959152e..8adeb979d 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/UnsignedSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnsignedSerializers.java
@@ -32,7 +32,8 @@ import org.apache.fory.type.unsigned.Uint8;
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class UnsignedSerializers {
 
-  public static final class Uint8Serializer extends ImmutableSerializer<Uint8> 
{
+  public static final class Uint8Serializer extends ImmutableSerializer<Uint8>
+      implements Shareable {
     public Uint8Serializer(Config config) {
       super(config, Uint8.class, false);
     }
@@ -46,14 +47,10 @@ public class UnsignedSerializers {
     public Uint8 read(ReadContext readContext) {
       return new Uint8(readContext.getBuffer().readByte());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class Uint16Serializer extends 
ImmutableSerializer<Uint16> {
+  public static final class Uint16Serializer extends 
ImmutableSerializer<Uint16>
+      implements Shareable {
     public Uint16Serializer(Config config) {
       super(config, Uint16.class, false);
     }
@@ -67,14 +64,10 @@ public class UnsignedSerializers {
     public Uint16 read(ReadContext readContext) {
       return new Uint16(readContext.getBuffer().readInt16());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class Uint32Serializer extends 
ImmutableSerializer<Uint32> {
+  public static final class Uint32Serializer extends 
ImmutableSerializer<Uint32>
+      implements Shareable {
     public Uint32Serializer(Config config) {
       super(config, Uint32.class, false);
     }
@@ -88,14 +81,10 @@ public class UnsignedSerializers {
     public Uint32 read(ReadContext readContext) {
       return new Uint32(readContext.getBuffer().readInt32());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static final class Uint64Serializer extends 
ImmutableSerializer<Uint64> {
+  public static final class Uint64Serializer extends 
ImmutableSerializer<Uint64>
+      implements Shareable {
     public Uint64Serializer(Config config) {
       super(config, Uint64.class, false);
     }
@@ -109,11 +98,6 @@ public class UnsignedSerializers {
     public Uint64 read(ReadContext readContext) {
       return new Uint64(readContext.getBuffer().readInt64());
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
   public static void registerDefaultSerializers(TypeResolver resolver) {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java
index a7cbf3c83..a0143717c 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/PrimitiveListSerializers.java
@@ -39,21 +39,18 @@ import org.apache.fory.context.WriteContext;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.Platform;
 import org.apache.fory.resolver.TypeResolver;
+import org.apache.fory.serializer.Shareable;
 
 /** Serializers for primitive list types. */
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class PrimitiveListSerializers {
 
-  private abstract static class PrimitiveListSerializer<T> extends 
CollectionLikeSerializer<T> {
+  private abstract static class PrimitiveListSerializer<T> extends 
CollectionLikeSerializer<T>
+      implements Shareable {
     private PrimitiveListSerializer(TypeResolver typeResolver, Class<T> cls) {
       super(typeResolver, cls, false, false);
     }
 
-    @Override
-    public boolean shareable() {
-      return true;
-    }
-
     @Override
     public final Collection onCollectionWrite(WriteContext writeContext, T 
value) {
       throw new IllegalStateException("supportCodegenHook is disabled for " + 
type.getName());
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/type/unsigned/UnsignedSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/type/unsigned/UnsignedSerializers.java
index 926dd570b..1eb1b3505 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/type/unsigned/UnsignedSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/type/unsigned/UnsignedSerializers.java
@@ -20,10 +20,7 @@
 package org.apache.fory.type.unsigned;
 
 import org.apache.fory.config.Config;
-import org.apache.fory.context.ReadContext;
-import org.apache.fory.context.WriteContext;
 import org.apache.fory.resolver.TypeResolver;
-import org.apache.fory.serializer.ImmutableSerializer;
 
 /** Utility class for registering Uint wrapper type serializers. */
 public class UnsignedSerializers {
@@ -31,97 +28,13 @@ public class UnsignedSerializers {
   /** Register all Uint wrapper type serializers. */
   public static void registerSerializers(TypeResolver resolver) {
     Config config = resolver.getConfig();
-    resolver.registerSerializer(Uint8.class, new Uint8Serializer(config));
-    resolver.registerSerializer(Uint16.class, new Uint16Serializer(config));
-    resolver.registerSerializer(Uint32.class, new Uint32Serializer(config));
-    resolver.registerSerializer(Uint64.class, new Uint64Serializer(config));
-  }
-
-  /** Serializer for Uint8 wrapper type. */
-  public static class Uint8Serializer extends ImmutableSerializer<Uint8> {
-    public Uint8Serializer(Config config) {
-      super(config, Uint8.class);
-    }
-
-    @Override
-    public void write(WriteContext writeContext, Uint8 value) {
-      writeContext.getBuffer().writeByte(value.byteValue());
-    }
-
-    @Override
-    public Uint8 read(ReadContext readContext) {
-      return Uint8.valueOf(readContext.getBuffer().readByte());
-    }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
-  }
-
-  /** Serializer for Uint16 wrapper type. */
-  public static class Uint16Serializer extends ImmutableSerializer<Uint16> {
-    public Uint16Serializer(Config config) {
-      super(config, Uint16.class);
-    }
-
-    @Override
-    public void write(WriteContext writeContext, Uint16 value) {
-      writeContext.getBuffer().writeInt16((short) value.toInt());
-    }
-
-    @Override
-    public Uint16 read(ReadContext readContext) {
-      return Uint16.valueOf(readContext.getBuffer().readInt16());
-    }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
-  }
-
-  /** Serializer for Uint32 wrapper type. */
-  public static class Uint32Serializer extends ImmutableSerializer<Uint32> {
-    public Uint32Serializer(Config config) {
-      super(config, Uint32.class);
-    }
-
-    @Override
-    public void write(WriteContext writeContext, Uint32 value) {
-      writeContext.getBuffer().writeInt32((int) value.toLong());
-    }
-
-    @Override
-    public Uint32 read(ReadContext readContext) {
-      return Uint32.valueOf(readContext.getBuffer().readInt32());
-    }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
-  }
-
-  /** Serializer for Uint64 wrapper type. */
-  public static class Uint64Serializer extends ImmutableSerializer<Uint64> {
-    public Uint64Serializer(Config config) {
-      super(config, Uint64.class);
-    }
-
-    @Override
-    public void write(WriteContext writeContext, Uint64 value) {
-      writeContext.getBuffer().writeInt64(value.toLong());
-    }
-
-    @Override
-    public Uint64 read(ReadContext readContext) {
-      return Uint64.valueOf(readContext.getBuffer().readInt64());
-    }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
+    resolver.registerSerializer(
+        Uint8.class, new 
org.apache.fory.serializer.UnsignedSerializers.Uint8Serializer(config));
+    resolver.registerSerializer(
+        Uint16.class, new 
org.apache.fory.serializer.UnsignedSerializers.Uint16Serializer(config));
+    resolver.registerSerializer(
+        Uint32.class, new 
org.apache.fory.serializer.UnsignedSerializers.Uint32Serializer(config));
+    resolver.registerSerializer(
+        Uint64.class, new 
org.apache.fory.serializer.UnsignedSerializers.Uint64Serializer(config));
   }
 }
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java 
b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
index d46bfc022..83b13ba78 100644
--- 
a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
+++ 
b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
@@ -20,6 +20,7 @@
 package org.apache.fory.resolver;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNotSame;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertSame;
@@ -65,6 +66,7 @@ import org.apache.fory.resolver.longlongpkg.C3;
 import org.apache.fory.serializer.ObjectSerializer;
 import org.apache.fory.serializer.Serializer;
 import org.apache.fory.serializer.Serializers;
+import org.apache.fory.serializer.Shareable;
 import org.apache.fory.serializer.collection.CollectionSerializer;
 import org.apache.fory.serializer.collection.CollectionSerializers;
 import org.apache.fory.serializer.collection.MapSerializers;
@@ -654,6 +656,25 @@ public class ClassResolverTest extends ForyTestBase {
     }
   }
 
+  static class ShareableFooSerializer extends Serializer<Foo> implements 
Shareable {
+
+    public ShareableFooSerializer(TypeResolver typeResolver, Class<Foo> type) {
+      super(typeResolver.getConfig(), type);
+    }
+
+    @Override
+    public void write(WriteContext writeContext, Foo value) {
+      writeContext.getBuffer().writeInt32(value.f1);
+    }
+
+    @Override
+    public Foo read(ReadContext readContext) {
+      Foo foo = new Foo();
+      foo.f1 = readContext.getBuffer().readInt32();
+      return foo;
+    }
+  }
+
   @Test
   public void testFooCustomSerializer() {
     Fory fory = Fory.builder().withLanguage(Language.JAVA).build();
@@ -667,6 +688,40 @@ public class ClassResolverTest extends ForyTestBase {
         fory.getTypeResolver().getSerializer(foo.getClass()).getClass(), 
FooCustomSerializer.class);
   }
 
+  @Test
+  public void testShareableSerializerSharedAcrossRuntimes() {
+    ForyBuilder builder = 
Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(true);
+    finishBuilder(builder);
+    SharedRegistry sharedRegistry = new SharedRegistry();
+    Fory fory1 = new Fory(builder, ClassResolverTest.class.getClassLoader(), 
sharedRegistry);
+    Fory fory2 = new Fory(builder, ClassResolverTest.class.getClassLoader(), 
sharedRegistry);
+
+    ClassResolver resolver1 = (ClassResolver) fory1.getTypeResolver();
+    ClassResolver resolver2 = (ClassResolver) fory2.getTypeResolver();
+
+    resolver1.register(Foo.class, 101);
+    resolver2.register(Foo.class, 101);
+    resolver1.registerSerializer(Foo.class, ShareableFooSerializer.class);
+    resolver2.registerSerializer(Foo.class, ShareableFooSerializer.class);
+    resolver1.finishRegistration();
+    resolver2.finishRegistration();
+
+    Serializer<?> serializer1 = resolver1.getSerializer(Foo.class);
+    TypeInfo sharedTypeInfo = 
sharedRegistry.registeredTypeInfoCache.get(Foo.class);
+    assertNotNull(sharedTypeInfo);
+    assertSame(sharedRegistry.getRegisteredSerializer(Foo.class), serializer1);
+    assertSame(sharedTypeInfo, resolver1.getTypeInfo(Foo.class, false));
+
+    TypeInfo sharedTypeInfo2 = resolver2.getTypeInfo(Foo.class, false);
+    assertNotNull(sharedTypeInfo2);
+    assertSame(sharedTypeInfo2, sharedTypeInfo);
+    assertSame(sharedTypeInfo2.getSerializer(), serializer1);
+
+    Foo foo = new Foo();
+    foo.f1 = 123;
+    assertEquals(fory2.deserialize(fory1.serialize(foo)), foo);
+  }
+
   @Test
   public void testRejectIncompatibleCollectionAndMapSerializerRegistration() {
     Fory fory = 
Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build();
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/xlang/RegisterTest.java 
b/java/fory-core/src/test/java/org/apache/fory/xlang/RegisterTest.java
index 45f348c64..4f21613bc 100644
--- a/java/fory-core/src/test/java/org/apache/fory/xlang/RegisterTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/xlang/RegisterTest.java
@@ -31,8 +31,10 @@ import org.apache.fory.context.ReadContext;
 import org.apache.fory.context.WriteContext;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.MemoryUtils;
+import org.apache.fory.resolver.SharedRegistry;
 import org.apache.fory.resolver.TypeResolver;
 import org.apache.fory.serializer.Serializer;
+import org.apache.fory.serializer.Shareable;
 import org.apache.fory.type.Types;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -83,6 +85,13 @@ public class RegisterTest extends ForyTestBase {
     }
   }
 
+  private static class ShareableMyExtSerializer extends MyExtSerializer 
implements Shareable {
+
+    public ShareableMyExtSerializer(TypeResolver typeResolver, Class<MyExt> 
cls) {
+      super(typeResolver, cls);
+    }
+  }
+
   @Data
   static class MyWrapper {
     Color color;
@@ -146,6 +155,47 @@ public class RegisterTest extends ForyTestBase {
     Assert.assertEquals(intValue, 1);
   }
 
+  @Test
+  public void testShareableSerializerOverrideStaysLocal() {
+    ForyBuilder builder =
+        
Fory.builder().withLanguage(Language.XLANG).requireClassRegistration(true);
+    finishBuilder(builder);
+    SharedRegistry sharedRegistry = new SharedRegistry();
+    Fory fory1 = new Fory(builder, RegisterTest.class.getClassLoader(), 
sharedRegistry);
+    Fory fory2 = new Fory(builder, RegisterTest.class.getClassLoader(), 
sharedRegistry);
+
+    TypeResolver resolver1 = fory1.getTypeResolver();
+    TypeResolver resolver2 = fory2.getTypeResolver();
+
+    fory1.register(MyExt.class, 103);
+    fory2.register(MyExt.class, 103);
+    resolver1.registerSerializer(MyExt.class, ShareableMyExtSerializer.class);
+    resolver2.registerSerializer(MyExt.class, ShareableMyExtSerializer.class);
+
+    Assert.assertNotSame(
+        resolver1.getSerializer(MyExt.class), 
resolver2.getSerializer(MyExt.class));
+  }
+
+  @Test
+  public void testShareableInternalSerializerSharedAcrossRuntimes() {
+    ForyBuilder builder =
+        
Fory.builder().withLanguage(Language.XLANG).requireClassRegistration(true);
+    finishBuilder(builder);
+    SharedRegistry sharedRegistry = new SharedRegistry();
+    Fory fory1 = new Fory(builder, RegisterTest.class.getClassLoader(), 
sharedRegistry);
+    Fory fory2 = new Fory(builder, RegisterTest.class.getClassLoader(), 
sharedRegistry);
+
+    TypeResolver resolver1 = fory1.getTypeResolver();
+    TypeResolver resolver2 = fory2.getTypeResolver();
+
+    resolver1.registerInternalSerializer(
+        MyExt.class, new ShareableMyExtSerializer(resolver1, MyExt.class));
+    resolver2.registerInternalSerializer(
+        MyExt.class, new ShareableMyExtSerializer(resolver2, MyExt.class));
+
+    Assert.assertSame(resolver1.getSerializer(MyExt.class), 
resolver2.getSerializer(MyExt.class));
+  }
+
   private static void finishBuilder(ForyBuilder builder) {
     try {
       Method finish = ForyBuilder.class.getDeclaredMethod("finish");
diff --git 
a/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ByteStringSerializer.java
 
b/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ByteStringSerializer.java
index 7b291607a..1e5b94071 100644
--- 
a/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ByteStringSerializer.java
+++ 
b/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ByteStringSerializer.java
@@ -25,8 +25,9 @@ import org.apache.fory.context.ReadContext;
 import org.apache.fory.context.WriteContext;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.serializer.Serializer;
+import org.apache.fory.serializer.Shareable;
 
-public class ByteStringSerializer extends Serializer<ByteString> {
+public class ByteStringSerializer extends Serializer<ByteString> implements 
Shareable {
   public ByteStringSerializer(Config config, Class<ByteString> type) {
     super(config, type, true);
   }
@@ -63,9 +64,4 @@ public class ByteStringSerializer extends 
Serializer<ByteString> {
       return bytes;
     }
   }
-
-  @Override
-  public boolean shareable() {
-    return true;
-  }
 }
diff --git 
a/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ProtobufSerializer.java
 
b/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ProtobufSerializer.java
index 8ac442fe0..3fe85cec3 100644
--- 
a/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ProtobufSerializer.java
+++ 
b/java/fory-extensions/src/main/java/org/apache/fory/extension/serializer/ProtobufSerializer.java
@@ -34,10 +34,11 @@ import org.apache.fory.context.WriteContext;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.Platform;
 import org.apache.fory.serializer.Serializer;
+import org.apache.fory.serializer.Shareable;
 import org.apache.fory.util.unsafe._JDKAccess;
 
 @SuppressWarnings({"rawtypes", "unchecked"})
-public class ProtobufSerializer extends Serializer<Message> {
+public class ProtobufSerializer extends Serializer<Message> implements 
Shareable {
   private static final int SMALL_BYTES_THRESHOLD = 32;
 
   private static final ClassValue<MethodHandle[]> parseFromMethodCache =
@@ -123,9 +124,4 @@ public class ProtobufSerializer extends Serializer<Message> 
{
       throw new RuntimeException(e);
     }
   }
-
-  @Override
-  public boolean shareable() {
-    return true;
-  }
 }
diff --git 
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/DurationSerializer.kt
 
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/DurationSerializer.kt
index 9b2f18a58..3854ee888 100644
--- 
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/DurationSerializer.kt
+++ 
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/DurationSerializer.kt
@@ -33,9 +33,10 @@ import org.apache.fory.context.ReadContext
 import org.apache.fory.context.WriteContext
 import org.apache.fory.serializer.ImmutableSerializer
 import org.apache.fory.serializer.Serializer
+import org.apache.fory.serializer.Shareable
 
 public class DurationSerializer(config: Config) :
-  ImmutableSerializer<Duration>(config, Duration::class.java, 
!config.isTimeRefIgnored) {
+  ImmutableSerializer<Duration>(config, Duration::class.java, 
!config.isTimeRefIgnored), Shareable {
 
   @Suppress("UNCHECKED_CAST")
   private fun durationUnitSerializer(writeContext: WriteContext): 
Serializer<DurationUnit> {
@@ -121,6 +122,4 @@ public class DurationSerializer(config: Config) :
       return DurationUnit.DAYS
     }
   }
-
-  override fun shareable(): Boolean = true
 }
diff --git 
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedArraySerializers.kt
 
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedArraySerializers.kt
index f93efc1ab..e012ddb57 100644
--- 
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedArraySerializers.kt
+++ 
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedArraySerializers.kt
@@ -26,13 +26,14 @@ import org.apache.fory.context.ReadContext
 import org.apache.fory.context.WriteContext
 import org.apache.fory.resolver.TypeResolver
 import org.apache.fory.serializer.Serializer
+import org.apache.fory.serializer.Shareable
 import org.apache.fory.serializer.collection.CollectionLikeSerializer
 
 public abstract class AbstractDelegatingArraySerializer<T, T_Delegate>(
   typeResolver: TypeResolver,
   cls: Class<T>,
   private val delegateClass: Class<T_Delegate>
-) : CollectionLikeSerializer<T>(typeResolver, cls, false) {
+) : CollectionLikeSerializer<T>(typeResolver, cls, false), Shareable {
 
   @Suppress("UNCHECKED_CAST")
   private fun delegatingSerializer(writeContext: WriteContext): 
Serializer<T_Delegate> {
@@ -64,8 +65,6 @@ public abstract class AbstractDelegatingArraySerializer<T, 
T_Delegate>(
   override fun onCollectionRead(collection: Collection<*>): T {
     throw IllegalStateException("supportCodegenHook is disabled for 
${type.name}")
   }
-
-  override fun shareable(): Boolean = true
 }
 
 public class UByteArraySerializer(
diff --git 
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedSerializer.kt
 
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedSerializer.kt
index b13b449b3..9f7f3c661 100644
--- 
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedSerializer.kt
+++ 
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UnsignedSerializer.kt
@@ -23,6 +23,7 @@ import org.apache.fory.config.Config
 import org.apache.fory.context.ReadContext
 import org.apache.fory.context.WriteContext
 import org.apache.fory.serializer.ImmutableSerializer
+import org.apache.fory.serializer.Shareable
 
 /**
  * UByteSerializer
@@ -31,13 +32,7 @@ import org.apache.fory.serializer.ImmutableSerializer
  */
 public class UByteSerializer(
   config: Config,
-) :
-  ImmutableSerializer<UByte>(
-    config,
-    UByte::class.java,
-    false,
-    true
-  ) {
+) : ImmutableSerializer<UByte>(config, UByte::class.java, false, true), 
Shareable {
 
   override fun write(writeContext: WriteContext, value: UByte) {
     writeContext.buffer.writeByte(value.toInt())
@@ -46,8 +41,6 @@ public class UByteSerializer(
   override fun read(readContext: ReadContext): UByte {
     return readContext.buffer.readByte().toUByte()
   }
-
-  override fun shareable(): Boolean = true
 }
 
 /**
@@ -57,13 +50,7 @@ public class UByteSerializer(
  */
 public class UShortSerializer(
   config: Config,
-) :
-  ImmutableSerializer<UShort>(
-    config,
-    UShort::class.java,
-    false,
-    true
-  ) {
+) : ImmutableSerializer<UShort>(config, UShort::class.java, false, true), 
Shareable {
   override fun write(writeContext: WriteContext, value: UShort) {
     writeContext.buffer.writeVarUint32(value.toInt())
   }
@@ -71,8 +58,6 @@ public class UShortSerializer(
   override fun read(readContext: ReadContext): UShort {
     return readContext.buffer.readVarUint32().toUShort()
   }
-
-  override fun shareable(): Boolean = true
 }
 
 /**
@@ -82,13 +67,7 @@ public class UShortSerializer(
  */
 public class UIntSerializer(
   config: Config,
-) :
-  ImmutableSerializer<UInt>(
-    config,
-    UInt::class.java,
-    false,
-    true
-  ) {
+) : ImmutableSerializer<UInt>(config, UInt::class.java, false, true), 
Shareable {
 
   override fun write(writeContext: WriteContext, value: UInt) {
     writeContext.buffer.writeVarUint32(value.toInt())
@@ -97,8 +76,6 @@ public class UIntSerializer(
   override fun read(readContext: ReadContext): UInt {
     return readContext.buffer.readVarUint32().toUInt()
   }
-
-  override fun shareable(): Boolean = true
 }
 
 /**
@@ -108,13 +85,7 @@ public class UIntSerializer(
  */
 public class ULongSerializer(
   config: Config,
-) :
-  ImmutableSerializer<ULong>(
-    config,
-    ULong::class.java,
-    false,
-    true
-  ) {
+) : ImmutableSerializer<ULong>(config, ULong::class.java, false, true), 
Shareable {
   override fun write(writeContext: WriteContext, value: ULong) {
     writeContext.buffer.writeVarUint64(value.toLong())
   }
@@ -122,6 +93,4 @@ public class ULongSerializer(
   override fun read(readContext: ReadContext): ULong {
     return readContext.buffer.readVarUint64().toULong()
   }
-
-  override fun shareable(): Boolean = true
 }
diff --git 
a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UuidSerializer.kt 
b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UuidSerializer.kt
index b4ca00920..94871abf0 100644
--- a/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UuidSerializer.kt
+++ b/kotlin/src/main/kotlin/org/apache/fory/serializer/kotlin/UuidSerializer.kt
@@ -25,9 +25,11 @@ import org.apache.fory.config.Config
 import org.apache.fory.context.ReadContext
 import org.apache.fory.context.WriteContext
 import org.apache.fory.serializer.ImmutableSerializer
+import org.apache.fory.serializer.Shareable
 
 @OptIn(ExperimentalUuidApi::class)
-public class UuidSerializer(config: Config) : 
ImmutableSerializer<Uuid>(config, Uuid::class.java) {
+public class UuidSerializer(config: Config) :
+  ImmutableSerializer<Uuid>(config, Uuid::class.java), Shareable {
   override fun write(writeContext: WriteContext, value: Uuid) {
     val buffer = writeContext.buffer
     value.toLongs { msb, lsb ->
@@ -40,6 +42,4 @@ public class UuidSerializer(config: Config) : 
ImmutableSerializer<Uuid>(config,
     val buffer = readContext.buffer
     return Uuid.fromLongs(buffer.readInt64(), buffer.readInt64())
   }
-
-  override fun shareable(): Boolean = true
 }
diff --git 
a/scala/src/main/java/org/apache/fory/serializer/scala/ToFactorySerializers.java
 
b/scala/src/main/java/org/apache/fory/serializer/scala/ToFactorySerializers.java
index 51097c496..b4f0901c3 100644
--- 
a/scala/src/main/java/org/apache/fory/serializer/scala/ToFactorySerializers.java
+++ 
b/scala/src/main/java/org/apache/fory/serializer/scala/ToFactorySerializers.java
@@ -25,6 +25,7 @@ import org.apache.fory.context.WriteContext;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.Platform;
 import org.apache.fory.reflect.ReflectionUtils;
+import org.apache.fory.serializer.Shareable;
 import org.apache.fory.serializer.Serializer;
 
 import java.lang.reflect.Field;
@@ -35,7 +36,7 @@ public class ToFactorySerializers  {
   static final Class<?> MapToFactoryClass = ReflectionUtils.loadClass(
     "scala.collection.MapFactory$ToFactory");
 
-  public static class IterableToFactorySerializer extends Serializer {
+  public static class IterableToFactorySerializer extends Serializer 
implements Shareable {
     private static final long fieldOffset;
 
     static {
@@ -63,14 +64,9 @@ public class ToFactorySerializers  {
       Platform.putObject(o, fieldOffset, readContext.readRef());
       return o;
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 
-  public static class MapToFactorySerializer extends Serializer {
+  public static class MapToFactorySerializer extends Serializer implements 
Shareable {
     private static final long fieldOffset;
 
     static {
@@ -98,10 +94,5 @@ public class ToFactorySerializers  {
       Platform.putObject(o, fieldOffset, readContext.readRef());
       return o;
     }
-
-    @Override
-    public boolean shareable() {
-      return true;
-    }
   }
 }
diff --git 
a/scala/src/main/scala/org/apache/fory/serializer/scala/RangeSerializer.scala 
b/scala/src/main/scala/org/apache/fory/serializer/scala/RangeSerializer.scala
index 1739e76eb..70d5fed92 100644
--- 
a/scala/src/main/scala/org/apache/fory/serializer/scala/RangeSerializer.scala
+++ 
b/scala/src/main/scala/org/apache/fory/serializer/scala/RangeSerializer.scala
@@ -22,6 +22,7 @@ package org.apache.fory.serializer.scala
 import org.apache.fory.context.ReadContext
 import org.apache.fory.context.WriteContext
 import org.apache.fory.reflect.FieldAccessor
+import org.apache.fory.serializer.Shareable
 import org.apache.fory.serializer.Serializer
 import org.apache.fory.serializer.collection.CollectionLikeSerializer
 import org.apache.fory.resolver.TypeResolver
@@ -32,7 +33,8 @@ import java.lang.invoke.{MethodHandle, MethodHandles}
 import scala.collection.immutable.NumericRange
 
 class RangeSerializer[T <: Range](typeResolver: TypeResolver, cls: Class[T])
-  extends CollectionLikeSerializer[T](typeResolver, cls, false) {
+  extends CollectionLikeSerializer[T](typeResolver, cls, false)
+  with Shareable {
   private val rangeClass = cls
 
   override def write(writeContext: WriteContext, value: T): Unit = {
@@ -52,9 +54,6 @@ class RangeSerializer[T <: Range](typeResolver: TypeResolver, 
cls: Class[T])
       Range.inclusive(start, end, step).asInstanceOf[T]
     }
   }
-
-  override def shareable(): Boolean = true
-
   override def onCollectionWrite(writeContext: WriteContext, value: T): 
util.Collection[_] =
     throw new IllegalStateException(s"supportCodegenHook is disabled for 
${getType.getName}")
 
@@ -74,7 +73,8 @@ private object RangeUtils {
 
 
 class NumericRangeSerializer[A, T <: NumericRange[A]](typeResolver: 
TypeResolver, cls: Class[T])
-  extends CollectionLikeSerializer[T](typeResolver, cls, false) {
+  extends CollectionLikeSerializer[T](typeResolver, cls, false)
+  with Shareable {
   private val ctr = RangeUtils.lookupCache.get(cls)
   private val getter =
     FieldAccessor.createAccessor(
@@ -101,9 +101,6 @@ class NumericRangeSerializer[A, T <: 
NumericRange[A]](typeResolver: TypeResolver
     val step = serializer.read(readContext)
     ctr.invoke(start, end, step, readContext.readRef()).asInstanceOf[T]
   }
-
-  override def shareable(): Boolean = true
-
   override def onCollectionWrite(writeContext: WriteContext, value: T): 
util.Collection[_] =
     throw new IllegalStateException(s"supportCodegenHook is disabled for 
${getType.getName}")
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to