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 f5a114b6a feat(java): remove guava dependency from fory java (#3557)
f5a114b6a is described below

commit f5a114b6a6a3fe35bec2c8dd6edcdca7cba93887
Author: Shawn Yang <[email protected]>
AuthorDate: Sun Apr 12 20:31:43 2026 +0800

    feat(java): remove guava dependency from fory java (#3557)
    
    ## Why?
    
    Make `fory-core` usable without requiring Guava on the runtime classpath
    while keeping Guava
    collection support available when the dependency is present.
    
    ## What does this PR do?
    
    - Marks the `guava` dependency in `java/fory-core` as optional.
    - Replaces the remaining Guava-based core utilities with local
    `org.apache.fory.collection`
    implementations for the cache, map maker, bi-map, weak-reference
    cleanup, and related helpers.
    - Keeps Guava collection serializers available behind runtime detection
    and preserves reserved type IDs
      when Guava registration is disabled or Guava is absent.
    - Moves Guava collection serializer coverage into `java/fory-testsuite`,
    adds explicit test-scope Guava
    dependencies there and in `java/fory-format`, and adds
    `GuavaOptionalDependencyTest` for the
      no-Guava classpath case.
    - Removes a few incidental Guava usages from Java benchmarks/tests and
    updates native-image
      initialization metadata for the new collection classes.
    
    ## Related issues
    Closes #1113
    #1114 #1335
    
    ## 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] `fory-core` no longer requires Guava on the runtime classpath;
    Guava-specific serializers remain available when Guava is present.
    
    - [x] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    Not run.
---
 .../fory/benchmark/state/FlatBuffersState.java     |   2 +-
 .../fory/benchmark/state/ProtoBuffersState.java    |   2 +-
 .../org/apache/fory/benchmark/state/JsonTest.java  |  24 +-
 java/fory-core/pom.xml                             |   1 +
 .../java/org/apache/fory/collection/BiMap.java     |  69 +++++
 .../java/org/apache/fory/collection/Cache.java     |  34 +++
 .../org/apache/fory/collection/CacheBuilder.java   | 101 ++++++
 .../apache/fory/collection/ClassValueCache.java    |   2 -
 .../org/apache/fory/collection/Collections.java    |   3 -
 .../java/org/apache/fory/collection/MapMaker.java  |  48 +++
 .../apache/fory/collection/MultiKeyWeakMap.java    |  47 ++-
 .../fory/collection/ReferenceConcurrentMap.java    | 338 +++++++++++++++++++++
 .../java/org/apache/fory/config/ForyBuilder.java   |   4 +-
 .../org/apache/fory/resolver/ClassResolver.java    |  32 +-
 .../org/apache/fory/resolver/SharedRegistry.java   |   2 +-
 .../org/apache/fory/resolver/TypeResolver.java     |   6 +-
 .../org/apache/fory/serializer/Serializers.java    |   4 +-
 .../collection/GuavaCollectionSerializers.java     | 108 +++++--
 .../serializer/collection/MapLikeSerializer.java   |  25 --
 .../fory/serializer/converter/FieldConverters.java |  21 +-
 .../main/java/org/apache/fory/type/Descriptor.java |   4 +-
 .../fory-core/native-image.properties              |  77 +----
 .../apache/fory/GuavaOptionalDependencyTest.java   | 146 +++++++++
 java/fory-format/pom.xml                           |   5 +
 java/fory-testsuite/pom.xml                        |   5 +
 .../collection/GuavaCollectionSerializersTest.java | 103 +++++--
 .../fory/serializer/scala/ScalaDispatcher.java     |   2 +-
 27 files changed, 1028 insertions(+), 187 deletions(-)

diff --git 
a/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/FlatBuffersState.java
 
b/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/FlatBuffersState.java
index 6ef412481..492e6521e 100644
--- 
a/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/FlatBuffersState.java
+++ 
b/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/FlatBuffersState.java
@@ -19,7 +19,6 @@
 
 package org.apache.fory.benchmark.state;
 
-import com.google.common.base.Preconditions;
 import com.google.flatbuffers.FlatBufferBuilder;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -40,6 +39,7 @@ import org.apache.fory.benchmark.state.generated.FBSMedia;
 import org.apache.fory.benchmark.state.generated.FBSMediaContent;
 import org.apache.fory.benchmark.state.generated.FBSSample;
 import org.apache.fory.memory.ByteBufferUtil;
+import org.apache.fory.util.Preconditions;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Param;
diff --git 
a/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/ProtoBuffersState.java
 
b/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/ProtoBuffersState.java
index 7fc94e1e9..d581c2b30 100644
--- 
a/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/ProtoBuffersState.java
+++ 
b/benchmarks/java/src/main/java/org/apache/fory/benchmark/state/ProtoBuffersState.java
@@ -19,7 +19,6 @@
 
 package org.apache.fory.benchmark.state;
 
-import com.google.common.base.Preconditions;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.util.HashMap;
 import java.util.function.Function;
@@ -31,6 +30,7 @@ import org.apache.fory.benchmark.data.Sample;
 import org.apache.fory.benchmark.state.Example.Bar;
 import org.apache.fory.benchmark.state.Example.Foo;
 import org.apache.fory.integration_tests.state.generated.ProtoMessage;
+import org.apache.fory.util.Preconditions;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.openjdk.jmh.annotations.CompilerControl;
 import org.openjdk.jmh.annotations.Fork;
diff --git 
a/benchmarks/java/src/test/java/org/apache/fory/benchmark/state/JsonTest.java 
b/benchmarks/java/src/test/java/org/apache/fory/benchmark/state/JsonTest.java
index 48e03ba57..b28f1cae7 100644
--- 
a/benchmarks/java/src/test/java/org/apache/fory/benchmark/state/JsonTest.java
+++ 
b/benchmarks/java/src/test/java/org/apache/fory/benchmark/state/JsonTest.java
@@ -20,8 +20,7 @@
 package org.apache.fory.benchmark.state;
 
 import com.alibaba.fastjson2.JSONObject;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.List;
 import org.apache.fory.Fory;
 import org.apache.fory.collection.Collections;
@@ -44,15 +43,18 @@ public class JsonTest {
 
   @DataProvider
   public static Object[][] config() {
-    return Sets.cartesianProduct(
-            ImmutableSet.of(true, false), // referenceTracking
-            ImmutableSet.of(true, false), // compatible mode
-            ImmutableSet.of(true, false), // scoped meta share mode
-            ImmutableSet.of(true, false) // fory enable codegen
-            )
-        .stream()
-        .map(List::toArray)
-        .toArray(Object[][]::new);
+    boolean[] options = new boolean[] {true, false};
+    List<Object[]> configs = new ArrayList<>(16);
+    for (boolean trackingRef : options) {
+      for (boolean compatible : options) {
+        for (boolean scoped : options) {
+          for (boolean codegen : options) {
+            configs.add(new Object[] {trackingRef, compatible, scoped, 
codegen});
+          }
+        }
+      }
+    }
+    return configs.toArray(new Object[0][]);
   }
 
   @Test(dataProvider = "config")
diff --git a/java/fory-core/pom.xml b/java/fory-core/pom.xml
index d6fd1b60c..8163a7130 100644
--- a/java/fory-core/pom.xml
+++ b/java/fory-core/pom.xml
@@ -50,6 +50,7 @@
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
+      <optional>true</optional>
     </dependency>
     <dependency>
       <groupId>org.codehaus.janino</groupId>
diff --git a/java/fory-core/src/main/java/org/apache/fory/collection/BiMap.java 
b/java/fory-core/src/main/java/org/apache/fory/collection/BiMap.java
new file mode 100644
index 000000000..e483075fd
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/BiMap.java
@@ -0,0 +1,69 @@
+/*
+ * 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.collection;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/** Minimal bidirectional map backed by caller-provided forward and reverse 
maps. */
+public final class BiMap<K, V> {
+  private final Map<K, V> valuesByKey;
+  private final Map<V, K> keysByValue;
+  private final Inverse inverse = new Inverse();
+
+  public BiMap(Map<K, V> valuesByKey, Map<V, K> keysByValue) {
+    this.valuesByKey = valuesByKey;
+    this.keysByValue = keysByValue;
+  }
+
+  public static <K, V> BiMap<K, V> newHashIdentityBiMap(int expectedSize) {
+    return new BiMap<>(new HashMap<>(expectedSize), new 
IdentityHashMap<>(expectedSize));
+  }
+
+  public V get(K key) {
+    return valuesByKey.get(key);
+  }
+
+  public boolean containsKey(K key) {
+    return valuesByKey.containsKey(key);
+  }
+
+  public void put(K key, V value) {
+    valuesByKey.put(key, value);
+    keysByValue.put(value, key);
+  }
+
+  public Inverse inverse() {
+    return inverse;
+  }
+
+  public final class Inverse {
+    private Inverse() {}
+
+    public boolean containsKey(V value) {
+      return keysByValue.containsKey(value);
+    }
+
+    public K get(V value) {
+      return keysByValue.get(value);
+    }
+  }
+}
diff --git a/java/fory-core/src/main/java/org/apache/fory/collection/Cache.java 
b/java/fory-core/src/main/java/org/apache/fory/collection/Cache.java
new file mode 100644
index 000000000..c079597ef
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Cache.java
@@ -0,0 +1,34 @@
+/*
+ * 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.collection;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+/** Minimal cache API used by fory-core. */
+public interface Cache<K, V> {
+  V getIfPresent(K key);
+
+  V get(K key, Callable<? extends V> loader) throws ExecutionException;
+
+  void put(K key, V value);
+
+  void cleanUp();
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/collection/CacheBuilder.java 
b/java/fory-core/src/main/java/org/apache/fory/collection/CacheBuilder.java
new file mode 100644
index 000000000..445984821
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/CacheBuilder.java
@@ -0,0 +1,101 @@
+/*
+ * 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.collection;
+
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import org.apache.fory.util.Preconditions;
+
+/** Minimal cache builder supporting the subset used by fory-core. */
+public final class CacheBuilder<K, V> {
+  private boolean weakKeys;
+  private boolean softValues;
+  private int concurrencyLevel = 4;
+
+  private CacheBuilder() {}
+
+  public static CacheBuilder<Object, Object> newBuilder() {
+    return new CacheBuilder<>();
+  }
+
+  public CacheBuilder<K, V> weakKeys() {
+    weakKeys = true;
+    return this;
+  }
+
+  public CacheBuilder<K, V> softValues() {
+    softValues = true;
+    return this;
+  }
+
+  public CacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {
+    Preconditions.checkArgument(concurrencyLevel > 0, "concurrencyLevel must 
be positive");
+    this.concurrencyLevel = concurrencyLevel;
+    return this;
+  }
+
+  public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
+    return new LocalCache<>(new ReferenceConcurrentMap<>(weakKeys, softValues, 
concurrencyLevel));
+  }
+
+  private static final class LocalCache<K, V> implements Cache<K, V> {
+    private final ReferenceConcurrentMap<K, V> cache;
+
+    private LocalCache(ReferenceConcurrentMap<K, V> cache) {
+      this.cache = cache;
+    }
+
+    @Override
+    public V getIfPresent(K key) {
+      return cache.get(key);
+    }
+
+    @Override
+    public V get(K key, Callable<? extends V> loader) throws 
ExecutionException {
+      Objects.requireNonNull(loader);
+      V value = cache.get(key);
+      if (value != null) {
+        return value;
+      }
+      V loadedValue;
+      try {
+        loadedValue = loader.call();
+      } catch (Exception e) {
+        throw new ExecutionException(e);
+      }
+      if (loadedValue == null) {
+        return null;
+      }
+      V existing = cache.putIfAbsent(key, loadedValue);
+      return existing != null ? existing : loadedValue;
+    }
+
+    @Override
+    public void put(K key, V value) {
+      cache.put(key, value);
+    }
+
+    @Override
+    public void cleanUp() {
+      cache.cleanUp();
+    }
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/collection/ClassValueCache.java 
b/java/fory-core/src/main/java/org/apache/fory/collection/ClassValueCache.java
index b511fd27c..7b16fce0d 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/collection/ClassValueCache.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/collection/ClassValueCache.java
@@ -19,8 +19,6 @@
 
 package org.apache.fory.collection;
 
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import org.apache.fory.annotation.Internal;
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/collection/Collections.java 
b/java/fory-core/src/main/java/org/apache/fory/collection/Collections.java
index a3f0232c6..2d3d95655 100644
--- a/java/fory-core/src/main/java/org/apache/fory/collection/Collections.java
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/Collections.java
@@ -19,9 +19,6 @@
 
 package org.apache.fory.collection;
 
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.collect.MapMaker;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/collection/MapMaker.java 
b/java/fory-core/src/main/java/org/apache/fory/collection/MapMaker.java
new file mode 100644
index 000000000..eacb1029b
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/collection/MapMaker.java
@@ -0,0 +1,48 @@
+/*
+ * 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.collection;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.fory.util.Preconditions;
+
+/** Minimal map builder supporting the subset used by fory-core. */
+public final class MapMaker {
+  private boolean weakKeys;
+  private int concurrencyLevel = 4;
+
+  public MapMaker weakKeys() {
+    weakKeys = true;
+    return this;
+  }
+
+  public MapMaker concurrencyLevel(int concurrencyLevel) {
+    Preconditions.checkArgument(concurrencyLevel > 0, "concurrencyLevel must 
be positive");
+    this.concurrencyLevel = concurrencyLevel;
+    return this;
+  }
+
+  public <K, V> Map<K, V> makeMap() {
+    if (weakKeys) {
+      return new ReferenceConcurrentMap<>(true, false, concurrencyLevel);
+    }
+    return new ConcurrentHashMap<>(Math.max(16, concurrencyLevel));
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/collection/MultiKeyWeakMap.java 
b/java/fory-core/src/main/java/org/apache/fory/collection/MultiKeyWeakMap.java
index 096497293..4efb254a4 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/collection/MultiKeyWeakMap.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/collection/MultiKeyWeakMap.java
@@ -19,8 +19,8 @@
 
 package org.apache.fory.collection;
 
-import com.google.common.base.FinalizableReferenceQueue;
-import com.google.common.base.FinalizableWeakReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.fory.util.GraalvmSupport;
@@ -42,7 +43,8 @@ import org.apache.fory.util.GraalvmSupport;
  */
 public class MultiKeyWeakMap<T> {
   private static final Set<KeyReference> REFERENCES = 
ConcurrentHashMap.newKeySet();
-  private static volatile FinalizableReferenceQueue referenceQueue;
+  private static final AtomicBoolean CLEANER_STARTED = new 
AtomicBoolean(false);
+  private static volatile ReferenceQueue<Object> referenceQueue;
   private final Map<Object, T> map;
 
   public MultiKeyWeakMap() {
@@ -76,22 +78,49 @@ public class MultiKeyWeakMap<T> {
     return keyRefs;
   }
 
-  private static FinalizableReferenceQueue getReferenceQueue() {
-    FinalizableReferenceQueue queue = referenceQueue;
+  private static ReferenceQueue<Object> getReferenceQueue() {
+    ReferenceQueue<Object> queue = referenceQueue;
     if (queue == null) {
       synchronized (MultiKeyWeakMap.class) {
         queue = referenceQueue;
         if (queue == null) {
-          queue = new FinalizableReferenceQueue();
+          queue = new ReferenceQueue<>();
           referenceQueue = queue;
+          startCleaner(queue);
         }
       }
     }
     return queue;
   }
 
+  private static void startCleaner(ReferenceQueue<Object> queue) {
+    if (!CLEANER_STARTED.compareAndSet(false, true)) {
+      return;
+    }
+    Thread cleaner =
+        new Thread(
+            () -> {
+              while (true) {
+                try {
+                  CleanupReference reference = (CleanupReference) 
queue.remove();
+                  reference.cleanup();
+                } catch (InterruptedException e) {
+                  Thread.currentThread().interrupt();
+                  return;
+                }
+              }
+            },
+            "fory-multi-key-weak-map-cleaner");
+    cleaner.setDaemon(true);
+    cleaner.start();
+  }
+
   private interface KeyReference {}
 
+  private interface CleanupReference {
+    void cleanup();
+  }
+
   private static final class NoCallbackRef implements KeyReference {
     private final Object obj;
 
@@ -117,8 +146,8 @@ public class MultiKeyWeakMap<T> {
     }
   }
 
-  private final class FinalizableKeyReference extends 
FinalizableWeakReference<Object>
-      implements KeyReference {
+  private final class FinalizableKeyReference extends WeakReference<Object>
+      implements KeyReference, CleanupReference {
     private final boolean[] reclaimedFlags;
     private final int index;
     private final List<FinalizableKeyReference> keyRefs;
@@ -135,7 +164,7 @@ public class MultiKeyWeakMap<T> {
     }
 
     @Override
-    public void finalizeReferent() {
+    public void cleanup() {
       reclaimedFlags[index] = true;
       REFERENCES.remove(this);
       if (IntStream.range(0, reclaimedFlags.length).allMatch(i -> 
reclaimedFlags[i])) {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/collection/ReferenceConcurrentMap.java
 
b/java/fory-core/src/main/java/org/apache/fory/collection/ReferenceConcurrentMap.java
new file mode 100644
index 000000000..8d40b4ed7
--- /dev/null
+++ 
b/java/fory-core/src/main/java/org/apache/fory/collection/ReferenceConcurrentMap.java
@@ -0,0 +1,338 @@
+/*
+ * 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.collection;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Concurrent map implementation supporting weak keys and optional soft values.
+ *
+ * <p>The implementation only covers the behaviors needed by fory-core.
+ */
+final class ReferenceConcurrentMap<K, V> extends AbstractMap<K, V> implements 
ConcurrentMap<K, V> {
+  private final boolean weakKeys;
+  private final boolean softValues;
+  private final ConcurrentHashMap<Object, Object> map;
+  private final ReferenceQueue<K> keyQueue;
+  private final ReferenceQueue<V> valueQueue;
+
+  ReferenceConcurrentMap(boolean weakKeys, boolean softValues, int 
concurrencyLevel) {
+    this.weakKeys = weakKeys;
+    this.softValues = softValues;
+    map = new ConcurrentHashMap<>(Math.max(16, concurrencyLevel));
+    keyQueue = weakKeys ? new ReferenceQueue<>() : null;
+    valueQueue = softValues ? new ReferenceQueue<>() : null;
+  }
+
+  void cleanUp() {
+    drainQueues();
+  }
+
+  @Override
+  public V get(Object key) {
+    drainQueues();
+    return dereferenceValue(map.get(lookupKey(key)));
+  }
+
+  @Override
+  public V put(K key, V value) {
+    Objects.requireNonNull(key);
+    Objects.requireNonNull(value);
+    drainQueues();
+    Object mapKey = storedKey(key);
+    return dereferenceValue(map.put(mapKey, storedValue(value, mapKey)));
+  }
+
+  @Override
+  public V putIfAbsent(K key, V value) {
+    Objects.requireNonNull(key);
+    Objects.requireNonNull(value);
+    while (true) {
+      drainQueues();
+      Object lookupKey = lookupKey(key);
+      Object current = map.get(lookupKey);
+      V currentValue = dereferenceValue(current);
+      if (currentValue != null) {
+        return currentValue;
+      }
+      Object mapKey = storedKey(key);
+      Object newValue = storedValue(value, mapKey);
+      if (current == null) {
+        Object existing = map.putIfAbsent(mapKey, newValue);
+        if (existing == null) {
+          return null;
+        }
+        current = existing;
+      } else if (map.replace(lookupKey, current, newValue)) {
+        return null;
+      }
+    }
+  }
+
+  @Override
+  public V remove(Object key) {
+    drainQueues();
+    return dereferenceValue(map.remove(lookupKey(key)));
+  }
+
+  @Override
+  public boolean remove(Object key, Object value) {
+    drainQueues();
+    Object lookupKey = lookupKey(key);
+    Object current = map.get(lookupKey);
+    if (current == null) {
+      return false;
+    }
+    V currentValue = dereferenceValue(current);
+    return currentValue != null
+        && Objects.equals(currentValue, value)
+        && map.remove(lookupKey, current);
+  }
+
+  @Override
+  public V replace(K key, V value) {
+    Objects.requireNonNull(key);
+    Objects.requireNonNull(value);
+    while (true) {
+      drainQueues();
+      Object lookupKey = lookupKey(key);
+      Object current = map.get(lookupKey);
+      if (current == null) {
+        return null;
+      }
+      V currentValue = dereferenceValue(current);
+      if (currentValue == null) {
+        continue;
+      }
+      Object mapKey = storedKey(key);
+      if (map.replace(lookupKey, current, storedValue(value, mapKey))) {
+        return currentValue;
+      }
+    }
+  }
+
+  @Override
+  public boolean replace(K key, V oldValue, V newValue) {
+    Objects.requireNonNull(key);
+    Objects.requireNonNull(oldValue);
+    Objects.requireNonNull(newValue);
+    while (true) {
+      drainQueues();
+      Object lookupKey = lookupKey(key);
+      Object current = map.get(lookupKey);
+      if (current == null) {
+        return false;
+      }
+      V currentValue = dereferenceValue(current);
+      if (currentValue == null) {
+        continue;
+      }
+      if (!Objects.equals(currentValue, oldValue)) {
+        return false;
+      }
+      Object mapKey = storedKey(key);
+      if (map.replace(lookupKey, current, storedValue(newValue, mapKey))) {
+        return true;
+      }
+    }
+  }
+
+  @Override
+  public boolean containsKey(Object key) {
+    return get(key) != null;
+  }
+
+  @Override
+  public void clear() {
+    map.clear();
+    cleanUp();
+  }
+
+  @Override
+  public int size() {
+    drainQueues();
+    return map.size();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    drainQueues();
+    return map.isEmpty();
+  }
+
+  @Override
+  public Set<Map.Entry<K, V>> entrySet() {
+    drainQueues();
+    Set<Map.Entry<K, V>> entries = new HashSet<>();
+    for (Map.Entry<Object, Object> entry : map.entrySet()) {
+      K key = dereferenceKey(entry.getKey());
+      V value = dereferenceValue(entry.getValue());
+      if (key != null && value != null) {
+        entries.add(new SimpleImmutableEntry<>(key, value));
+      }
+    }
+    return new EntrySet(entries);
+  }
+
+  private void drainQueues() {
+    if (weakKeys) {
+      WeakKeyReference<K> ref;
+      while ((ref = (WeakKeyReference<K>) keyQueue.poll()) != null) {
+        map.remove(ref);
+      }
+    }
+    if (softValues) {
+      SoftValueReference<V> ref;
+      while ((ref = (SoftValueReference<V>) valueQueue.poll()) != null) {
+        map.remove(ref.mapKey, ref);
+      }
+    }
+  }
+
+  private Object lookupKey(Object key) {
+    Objects.requireNonNull(key);
+    return weakKeys ? new LookupKey<>(key) : key;
+  }
+
+  private Object storedKey(K key) {
+    return weakKeys ? new WeakKeyReference<>(key, keyQueue) : key;
+  }
+
+  private Object storedValue(V value, Object mapKey) {
+    return softValues ? new SoftValueReference<>(value, mapKey, valueQueue) : 
value;
+  }
+
+  @SuppressWarnings("unchecked")
+  private K dereferenceKey(Object mapKey) {
+    if (!weakKeys) {
+      return (K) mapKey;
+    }
+    return ((WeakKeyReference<K>) mapKey).get();
+  }
+
+  @SuppressWarnings("unchecked")
+  private V dereferenceValue(Object storedValue) {
+    if (storedValue == null) {
+      return null;
+    }
+    if (!softValues) {
+      return (V) storedValue;
+    }
+    SoftValueReference<V> ref = (SoftValueReference<V>) storedValue;
+    V value = ref.get();
+    if (value == null) {
+      map.remove(ref.mapKey, ref);
+    }
+    return value;
+  }
+
+  private static final class EntrySet<K, V> extends AbstractSet<Map.Entry<K, 
V>> {
+    private final Set<Map.Entry<K, V>> entries;
+
+    private EntrySet(Set<Map.Entry<K, V>> entries) {
+      this.entries = entries;
+    }
+
+    @Override
+    public Iterator<Map.Entry<K, V>> iterator() {
+      return entries.iterator();
+    }
+
+    @Override
+    public int size() {
+      return entries.size();
+    }
+  }
+
+  private static final class LookupKey<K> {
+    private final K key;
+    private final int hashCode;
+
+    private LookupKey(K key) {
+      this.key = key;
+      hashCode = key.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (other instanceof LookupKey) {
+        return key.equals(((LookupKey<?>) other).key);
+      }
+      if (other instanceof WeakKeyReference) {
+        return key.equals(((WeakKeyReference<?>) other).get());
+      }
+      return key.equals(other);
+    }
+
+    @Override
+    public int hashCode() {
+      return hashCode;
+    }
+  }
+
+  private static final class WeakKeyReference<K> extends WeakReference<K> {
+    private final int hashCode;
+
+    private WeakKeyReference(K key, ReferenceQueue<K> queue) {
+      super(key, queue);
+      hashCode = key.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      K key = get();
+      if (key == null) {
+        return false;
+      }
+      if (other instanceof LookupKey) {
+        return key.equals(((LookupKey<?>) other).key);
+      }
+      if (other instanceof WeakKeyReference) {
+        return key.equals(((WeakKeyReference<?>) other).get());
+      }
+      return key.equals(other);
+    }
+
+    @Override
+    public int hashCode() {
+      return hashCode;
+    }
+  }
+
+  private static final class SoftValueReference<V> extends SoftReference<V> {
+    private final Object mapKey;
+
+    private SoftValueReference(V value, Object mapKey, ReferenceQueue<V> 
queue) {
+      super(value, queue);
+      this.mapKey = mapKey;
+    }
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java 
b/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java
index 044a9ac25..a6805c3ac 100644
--- a/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java
+++ b/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java
@@ -335,7 +335,9 @@ public final class ForyBuilder {
 
   /**
    * Whether pre-register guava types such as 
`RegularImmutableMap`/`RegularImmutableList`. Those
-   * types are not public API, but seems pretty stable.
+   * types are not public API, but seems pretty stable. When Guava is absent 
at runtime, enabling
+   * this option still reserves the Guava internal id block so later internal 
registrations keep the
+   * same ids.
    *
    * @see GuavaCollectionSerializers
    */
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 f82f6fd07..af4ec7f73 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
@@ -332,12 +332,7 @@ public class ClassResolver extends TypeResolver {
     if (SqlTimeSerializers.isSqlModuleAvailable()) {
       SqlTimeSerializers.registerDefaultSerializers(this);
     } else {
-      Preconditions.checkArgument(
-          extRegistry.classIdGenerator + 
SqlTimeSerializers.getNumReservedTypeIds()
-              <= INTERNAL_NATIVE_ID_LIMIT,
-          "Internal type id overflow: %s",
-          extRegistry.classIdGenerator + 
SqlTimeSerializers.getNumReservedTypeIds());
-      extRegistry.classIdGenerator += 
SqlTimeSerializers.getNumReservedTypeIds();
+      reserveInternalTypeIds(SqlTimeSerializers.getNumReservedTypeIds());
     }
     OptionalSerializers.registerDefaultSerializers(this);
     CollectionSerializers.registerDefaultSerializers(this);
@@ -362,7 +357,11 @@ public class ClassResolver extends TypeResolver {
     ImmutableCollectionSerializers.registerSerializers(this);
     SubListSerializers.registerSerializers(this, true);
     if (config.registerGuavaTypes()) {
-      GuavaCollectionSerializers.registerDefaultSerializers(this);
+      if (GuavaCollectionSerializers.isGuavaAvailable()) {
+        GuavaCollectionSerializers.registerDefaultSerializers(this);
+      } else {
+        
reserveInternalTypeIds(GuavaCollectionSerializers.getNumReservedTypeIds());
+      }
     }
     if (config.deserializeUnknownClass()) {
       if (metaContextShareEnabled) {
@@ -380,6 +379,25 @@ public class ClassResolver extends TypeResolver {
     registerInternal(type);
   }
 
+  private void reserveInternalTypeIds(int numTypeIds) {
+    Preconditions.checkArgument(numTypeIds >= 0, "numTypeIds must be 
non-negative");
+    for (int i = 0; i < numTypeIds; i++) {
+      Preconditions.checkArgument(
+          extRegistry.classIdGenerator < INTERNAL_NATIVE_ID_LIMIT,
+          "Internal type id overflow: %s",
+          extRegistry.classIdGenerator);
+      while (extRegistry.classIdGenerator < typeIdToTypeInfo.length
+          && typeIdToTypeInfo[extRegistry.classIdGenerator] != null) {
+        extRegistry.classIdGenerator++;
+      }
+      Preconditions.checkArgument(
+          extRegistry.classIdGenerator < INTERNAL_NATIVE_ID_LIMIT,
+          "Internal type id overflow: %s",
+          extRegistry.classIdGenerator);
+      extRegistry.classIdGenerator++;
+    }
+  }
+
   /** Register common class ahead to get smaller class id for serialization. */
   private void registerCommonUsedClasses() {
     registerInternal(LinkedList.class, TreeSet.class);
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 b7bc39378..8e6b50484 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
@@ -19,7 +19,6 @@
 
 package org.apache.fory.resolver;
 
-import com.google.common.collect.BiMap;
 import java.lang.reflect.Member;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -30,6 +29,7 @@ import java.util.SortedMap;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.fory.annotation.Internal;
 import org.apache.fory.codegen.CodeGenerator;
+import org.apache.fory.collection.BiMap;
 import org.apache.fory.collection.ConcurrentIdentityMap;
 import org.apache.fory.collection.Tuple2;
 import org.apache.fory.meta.EncodedMetaString;
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 f092cf52e..ef42e563a 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
@@ -22,8 +22,6 @@ package org.apache.fory.resolver;
 import static org.apache.fory.type.TypeUtils.getSizeOfPrimitiveType;
 import static org.apache.fory.type.Types.INVALID_USER_TYPE_ID;
 
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
 import java.lang.reflect.AnnotatedType;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
@@ -53,6 +51,7 @@ import org.apache.fory.builder.JITContext;
 import org.apache.fory.codegen.CodeGenerator;
 import org.apache.fory.codegen.Expression;
 import org.apache.fory.codegen.Expression.Invoke;
+import org.apache.fory.collection.BiMap;
 import org.apache.fory.collection.ConcurrentIdentityMap;
 import org.apache.fory.collection.IdentityMap;
 import org.apache.fory.collection.IdentityObjectIntMap;
@@ -1755,7 +1754,8 @@ public abstract class TypeResolver {
     // shared across multiple fory instances.
     IdentityHashMap<Class<?>, Integer> registeredClassIdMap =
         new IdentityHashMap<>(isCrossLanguage() ? 4 : 200);
-    BiMap<String, Class<?>> registeredClasses = 
HashBiMap.create(isCrossLanguage() ? 4 : 200);
+    BiMap<String, Class<?>> registeredClasses =
+        BiMap.newHashIdentityBiMap(isCrossLanguage() ? 4 : 200);
     final ConcurrentIdentityMap<Class<?>, TypeDef> currentLayerTypeDef;
     // TODO(chaokunyang) Better to  use soft reference, see ObjectStreamClass.
     final ConcurrentHashMap<Tuple2<Class<?>, Boolean>, SortedMap<Member, 
Descriptor>>
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 627075bc2..3e1437a9c 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
@@ -21,8 +21,6 @@ package org.apache.fory.serializer;
 
 import static org.apache.fory.util.function.Functions.makeGetterFunction;
 
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
@@ -42,6 +40,8 @@ import java.util.function.Function;
 import java.util.function.ToIntFunction;
 import java.util.regex.Pattern;
 import org.apache.fory.Fory;
+import org.apache.fory.collection.Cache;
+import org.apache.fory.collection.CacheBuilder;
 import org.apache.fory.collection.Tuple2;
 import org.apache.fory.config.Config;
 import org.apache.fory.context.CopyContext;
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
index d7b03d88b..00e7cb2bb 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
@@ -34,11 +34,32 @@ import org.apache.fory.context.CopyContext;
 import org.apache.fory.context.ReadContext;
 import org.apache.fory.context.WriteContext;
 import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.resolver.TypeInfo;
 import org.apache.fory.resolver.TypeResolver;
 
 /** Serializers for common guava types. */
 @SuppressWarnings({"unchecked", "rawtypes"})
 public class GuavaCollectionSerializers {
+  private static final String PKG = "com.google.common.collect";
+  private static final String IMMUTABLE_BI_MAP_CLASS_NAME = PKG + 
".ImmutableBiMap";
+  private static final String IMMUTABLE_LIST_CLASS_NAME = PKG + 
".ImmutableList";
+  private static final String IMMUTABLE_MAP_CLASS_NAME = PKG + ".ImmutableMap";
+  private static final String IMMUTABLE_SET_CLASS_NAME = PKG + ".ImmutableSet";
+  private static final String IMMUTABLE_SORTED_MAP_CLASS_NAME = PKG + 
".ImmutableSortedMap";
+  private static final String IMMUTABLE_SORTED_SET_CLASS_NAME = PKG + 
".ImmutableSortedSet";
+  private static final int NUM_RESERVED_TYPE_IDS = 13;
+  private static final boolean GUAVA_AVAILABLE =
+      isClassAvailable(IMMUTABLE_BI_MAP_CLASS_NAME)
+          && isClassAvailable(IMMUTABLE_LIST_CLASS_NAME)
+          && isClassAvailable(IMMUTABLE_MAP_CLASS_NAME)
+          && isClassAvailable(IMMUTABLE_SET_CLASS_NAME)
+          && isClassAvailable(IMMUTABLE_SORTED_MAP_CLASS_NAME)
+          && isClassAvailable(IMMUTABLE_SORTED_SET_CLASS_NAME);
+
+  private interface MapEntryBuilder {
+    void put(Object key, Object value);
+  }
+
   abstract static class GuavaCollectionSerializer<T extends Collection>
       extends CollectionSerializer<T> {
     public GuavaCollectionSerializer(TypeResolver typeResolver, Class<T> cls) {
@@ -82,8 +103,6 @@ public class GuavaCollectionSerializers {
     }
   }
 
-  private static final String pkg = "com.google.common.collect";
-
   public static final class RegularImmutableListSerializer<T extends 
ImmutableList>
       extends GuavaCollectionSerializer<T> {
     public RegularImmutableListSerializer(TypeResolver typeResolver, Class<T> 
cls) {
@@ -192,7 +211,6 @@ public class GuavaCollectionSerializers {
   }
 
   abstract static class GuavaMapSerializer<T extends Map> extends 
MapSerializer<T> {
-
     public GuavaMapSerializer(TypeResolver typeResolver, Class<T> cls) {
       super(typeResolver, cls, true);
       typeResolver.setSerializer(cls, this);
@@ -211,7 +229,8 @@ public class GuavaCollectionSerializers {
     @Override
     public T copy(CopyContext copyContext, T originMap) {
       Builder builder = makeBuilder(originMap.size());
-      copyEntry(copyContext, originMap, builder);
+      GuavaCollectionSerializers.copyEntries(
+          typeResolver, mapTypeCache(), copyContext, originMap, builder::put);
       return (T) builder.build();
     }
 
@@ -264,6 +283,40 @@ public class GuavaCollectionSerializers {
     return ImmutableBiMap.builder();
   }
 
+  public static boolean isGuavaAvailable() {
+    return GUAVA_AVAILABLE;
+  }
+
+  public static int getNumReservedTypeIds() {
+    return NUM_RESERVED_TYPE_IDS;
+  }
+
+  private static void copyEntries(
+      TypeResolver typeResolver,
+      MapLikeSerializer.MapTypeCache state,
+      CopyContext copyContext,
+      Map<?, ?> originMap,
+      MapEntryBuilder builder) {
+    for (Map.Entry<?, ?> entry : originMap.entrySet()) {
+      Object key = entry.getKey();
+      if (key != null) {
+        TypeInfo typeInfo = typeResolver.getTypeInfo(key.getClass(), 
state.keyTypeInfoWriteCache);
+        if (!typeInfo.getSerializer().isImmutable()) {
+          key = copyContext.copyObject(key, typeInfo.getTypeId());
+        }
+      }
+      Object value = entry.getValue();
+      if (value != null) {
+        TypeInfo typeInfo =
+            typeResolver.getTypeInfo(value.getClass(), 
state.valueTypeInfoWriteCache);
+        if (!typeInfo.getSerializer().isImmutable()) {
+          value = copyContext.copyObject(value, typeInfo.getTypeId());
+        }
+      }
+      builder.put(key, value);
+    }
+  }
+
   public static final class ImmutableMapSerializer<T extends ImmutableMap>
       extends GuavaMapSerializer<T> {
 
@@ -329,7 +382,7 @@ public class GuavaCollectionSerializers {
     public T copy(CopyContext copyContext, T originMap) {
       Comparator comparator = copyContext.copyObject(originMap.comparator());
       ImmutableSortedMap.Builder builder = new 
ImmutableSortedMap.Builder(comparator);
-      copyEntry(copyContext, originMap, builder);
+      copyEntries(typeResolver, mapTypeCache(), copyContext, originMap, 
builder::put);
       return (T) builder.build();
     }
 
@@ -367,6 +420,9 @@ public class GuavaCollectionSerializers {
   // UnmodifiableNavigableSet
 
   public static void registerDefaultSerializers(TypeResolver resolver) {
+    if (!GUAVA_AVAILABLE) {
+      throw new IllegalStateException("Guava classes are not available");
+    }
     // Note: Guava common types are not public API, don't register by 
`ImmutableXXX.of()`,
     // since different guava version may return different type objects, which 
make class
     // registration
@@ -374,31 +430,31 @@ public class GuavaCollectionSerializers {
     // For example: guava 20 return ImmutableBiMap for ImmutableMap.of(), but 
guava 27 return
     // ImmutableMap.
     Class cls =
-        loadClass(pkg + ".RegularImmutableBiMap", ImmutableBiMap.of("k1", 1, 
"k2", 4).getClass());
+        loadClass(PKG + ".RegularImmutableBiMap", ImmutableBiMap.of("k1", 1, 
"k2", 4).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableBiMapSerializer(resolver, cls));
-    cls = loadClass(pkg + ".SingletonImmutableBiMap", ImmutableBiMap.of(1, 
2).getClass());
+    cls = loadClass(PKG + ".SingletonImmutableBiMap", ImmutableBiMap.of(1, 
2).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableBiMapSerializer(resolver, cls));
-    cls = loadClass(pkg + ".RegularImmutableMap", ImmutableMap.of("k1", 1, 
"k2", 2).getClass());
+    cls = loadClass(PKG + ".RegularImmutableMap", ImmutableMap.of("k1", 1, 
"k2", 2).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableMapSerializer(resolver, cls));
-    cls = loadClass(pkg + ".RegularImmutableList", 
ImmutableList.of().getClass());
+    cls = loadClass(PKG + ".RegularImmutableList", 
ImmutableList.of().getClass());
     resolver.registerInternalSerializer(cls, new 
RegularImmutableListSerializer(resolver, cls));
-    cls = loadClass(pkg + ".SingletonImmutableList", 
ImmutableList.of(1).getClass());
+    cls = loadClass(PKG + ".SingletonImmutableList", 
ImmutableList.of(1).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableListSerializer(resolver, cls));
-    cls = loadClass(pkg + ".RegularImmutableSet", ImmutableSet.of(1, 
2).getClass());
+    cls = loadClass(PKG + ".RegularImmutableSet", ImmutableSet.of(1, 
2).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableSetSerializer(resolver, cls));
-    cls = loadClass(pkg + ".SingletonImmutableSet", 
ImmutableSet.of(1).getClass());
+    cls = loadClass(PKG + ".SingletonImmutableSet", 
ImmutableSet.of(1).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableSetSerializer(resolver, cls));
     // sorted set/map doesn't support xlang.
-    cls = loadClass(pkg + ".RegularImmutableSortedSet", 
ImmutableSortedSet.of(1, 2).getClass());
+    cls = loadClass(PKG + ".RegularImmutableSortedSet", 
ImmutableSortedSet.of(1, 2).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableSortedSetSerializer<>(resolver, cls));
-    cls = loadClass(pkg + ".ImmutableSortedMap", ImmutableSortedMap.of(1, 
2).getClass());
+    cls = loadClass(PKG + ".ImmutableSortedMap", ImmutableSortedMap.of(1, 
2).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableSortedMapSerializer<>(resolver, cls));
 
     // Guava version before 19.0, of() return
     // 
EmptyImmutableSet/EmptyImmutableBiMap/EmptyImmutableSortedMap/EmptyImmutableSortedSet
     // we register if class exist or register empty to deserialize.
-    if (checkClassExist(pkg + ".EmptyImmutableSet")) {
-      cls = loadClass(pkg + ".EmptyImmutableSet", 
ImmutableSet.of().getClass());
+    if (checkClassExist(PKG + ".EmptyImmutableSet")) {
+      cls = loadClass(PKG + ".EmptyImmutableSet", 
ImmutableSet.of().getClass());
       resolver.registerInternalSerializer(cls, new 
ImmutableSetSerializer(resolver, cls));
     } else {
       class GuavaEmptySet {}
@@ -406,8 +462,8 @@ public class GuavaCollectionSerializers {
       cls = GuavaEmptySet.class;
       resolver.registerInternalSerializer(cls, new 
ImmutableSetSerializer(resolver, cls));
     }
-    if (checkClassExist(pkg + ".EmptyImmutableBiMap")) {
-      cls = loadClass(pkg + ".EmptyImmutableBiMap", 
ImmutableBiMap.of().getClass());
+    if (checkClassExist(PKG + ".EmptyImmutableBiMap")) {
+      cls = loadClass(PKG + ".EmptyImmutableBiMap", 
ImmutableBiMap.of().getClass());
       resolver.registerInternalSerializer(cls, new 
ImmutableMapSerializer(resolver, cls));
     } else {
       class GuavaEmptyBiMap {}
@@ -415,8 +471,8 @@ public class GuavaCollectionSerializers {
       cls = GuavaEmptyBiMap.class;
       resolver.registerInternalSerializer(cls, new 
ImmutableMapSerializer(resolver, cls));
     }
-    if (checkClassExist(pkg + ".EmptyImmutableSortedSet")) {
-      cls = loadClass(pkg + ".EmptyImmutableSortedSet", 
ImmutableSortedSet.of().getClass());
+    if (checkClassExist(PKG + ".EmptyImmutableSortedSet")) {
+      cls = loadClass(PKG + ".EmptyImmutableSortedSet", 
ImmutableSortedSet.of().getClass());
       resolver.registerInternalSerializer(cls, new 
ImmutableSortedSetSerializer(resolver, cls));
     } else {
       class GuavaEmptySortedSet {}
@@ -424,8 +480,8 @@ public class GuavaCollectionSerializers {
       cls = GuavaEmptySortedSet.class;
       resolver.registerInternalSerializer(cls, new 
ImmutableSortedSetSerializer(resolver, cls));
     }
-    if (checkClassExist(pkg + ".EmptyImmutableSortedMap")) {
-      cls = loadClass(pkg + ".EmptyImmutableSortedMap", 
ImmutableSortedMap.of().getClass());
+    if (checkClassExist(PKG + ".EmptyImmutableSortedMap")) {
+      cls = loadClass(PKG + ".EmptyImmutableSortedMap", 
ImmutableSortedMap.of().getClass());
       resolver.registerInternalSerializer(cls, new 
ImmutableSortedMapSerializer(resolver, cls));
     } else {
       class GuavaEmptySortedMap {}
@@ -440,7 +496,7 @@ public class GuavaCollectionSerializers {
       return cache;
     } else {
       try {
-        return Class.forName(className);
+        return Class.forName(className, false, 
GuavaCollectionSerializers.class.getClassLoader());
       } catch (ClassNotFoundException e) {
         throw new RuntimeException(e);
       }
@@ -448,8 +504,12 @@ public class GuavaCollectionSerializers {
   }
 
   static boolean checkClassExist(String className) {
+    return isClassAvailable(className);
+  }
+
+  private static boolean isClassAvailable(String className) {
     try {
-      Class.forName(className);
+      Class.forName(className, false, 
GuavaCollectionSerializers.class.getClassLoader());
     } catch (ClassNotFoundException e) {
       return false;
     }
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
index 59c89cc12..81c0dc5aa 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
@@ -32,7 +32,6 @@ import static 
org.apache.fory.serializer.collection.MapFlags.VALUE_DECL_TYPE;
 import static org.apache.fory.serializer.collection.MapFlags.VALUE_HAS_NULL;
 import static org.apache.fory.type.TypeUtils.MAP_TYPE;
 
-import com.google.common.collect.ImmutableMap.Builder;
 import java.lang.invoke.MethodHandle;
 import java.util.Iterator;
 import java.util.Map;
@@ -596,30 +595,6 @@ public abstract class MapLikeSerializer<T> extends 
Serializer<T> {
     }
   }
 
-  protected <K, V> void copyEntry(
-      CopyContext copyContext, Map<K, V> originMap, Builder<K, V> builder) {
-    TypeResolver classResolver = typeResolver;
-    MapTypeCache state = mapTypeCache();
-    for (Entry<K, V> entry : originMap.entrySet()) {
-      K key = entry.getKey();
-      if (key != null) {
-        TypeInfo typeInfo = classResolver.getTypeInfo(key.getClass(), 
state.keyTypeInfoWriteCache);
-        if (!typeInfo.getSerializer().isImmutable()) {
-          key = copyContext.copyObject(key, typeInfo.getTypeId());
-        }
-      }
-      V value = entry.getValue();
-      if (value != null) {
-        TypeInfo typeInfo =
-            classResolver.getTypeInfo(value.getClass(), 
state.valueTypeInfoWriteCache);
-        if (!typeInfo.getSerializer().isImmutable()) {
-          value = copyContext.copyObject(value, typeInfo.getTypeId());
-        }
-      }
-      builder.put(key, value);
-    }
-  }
-
   protected <K, V> void copyEntry(CopyContext copyContext, Map<K, V> 
originMap, Object[] elements) {
     TypeResolver classResolver = typeResolver;
     MapTypeCache state = mapTypeCache();
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverters.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverters.java
index 87f7cdd5a..fc602f512 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverters.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/converter/FieldConverters.java
@@ -19,9 +19,9 @@
 
 package org.apache.fory.serializer.converter;
 
-import com.google.common.collect.ImmutableSet;
 import java.lang.reflect.Field;
 import java.util.Set;
+import org.apache.fory.collection.Collections;
 import org.apache.fory.reflect.FieldAccessor;
 import org.apache.fory.type.TypeUtils;
 
@@ -31,6 +31,9 @@ import org.apache.fory.type.TypeUtils;
  * automatic type conversion during serialization/deserialization processes.
  */
 public class FieldConverters {
+  private static Set<Class<?>> compatibleTypes(Class<?>... types) {
+    return java.util.Collections.unmodifiableSet(Collections.ofHashSet(types));
+  }
 
   /**
    * Creates an appropriate field converter based on the target field type and 
source object type.
@@ -128,7 +131,7 @@ public class FieldConverters {
    * false for null values and incompatible types.
    */
   public static class BooleanConverter extends FieldConverter<Boolean> {
-    static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class, 
Boolean.class);
+    static Set<Class<?>> compatibleTypes = compatibleTypes(String.class, 
Boolean.class);
 
     protected BooleanConverter(FieldAccessor fieldAccessor) {
       super(fieldAccessor);
@@ -193,7 +196,7 @@ public class FieldConverters {
    */
   public static class ByteConverter extends FieldConverter<Byte> {
     static Set<Class<?>> compatibleTypes =
-        ImmutableSet.of(String.class, Integer.class, Long.class, Short.class, 
Byte.class);
+        compatibleTypes(String.class, Integer.class, Long.class, Short.class, 
Byte.class);
 
     protected ByteConverter(FieldAccessor fieldAccessor) {
       super(fieldAccessor);
@@ -266,7 +269,7 @@ public class FieldConverters {
    */
   public static class ShortConverter extends FieldConverter<Short> {
     static Set<Class<?>> compatibleTypes =
-        ImmutableSet.of(String.class, Integer.class, Long.class, Short.class);
+        compatibleTypes(String.class, Integer.class, Long.class, Short.class);
 
     protected ShortConverter(FieldAccessor fieldAccessor) {
       super(fieldAccessor);
@@ -330,7 +333,7 @@ public class FieldConverters {
    * values.
    */
   public static class IntConverter extends FieldConverter<Integer> {
-    static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class, 
Long.class, Integer.class);
+    static Set<Class<?>> compatibleTypes = compatibleTypes(String.class, 
Long.class, Integer.class);
 
     protected IntConverter(FieldAccessor fieldAccessor) {
       super(fieldAccessor);
@@ -398,7 +401,7 @@ public class FieldConverters {
    * null values.
    */
   public static class LongConverter extends FieldConverter<Long> {
-    static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class, 
Long.class);
+    static Set<Class<?>> compatibleTypes = compatibleTypes(String.class, 
Long.class);
 
     protected LongConverter(FieldAccessor fieldAccessor) {
       super(fieldAccessor);
@@ -463,7 +466,7 @@ public class FieldConverters {
    * for null values. Only allows conversion from String.
    */
   public static class FloatConverter extends FieldConverter<Float> {
-    static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class, 
Float.class);
+    static Set<Class<?>> compatibleTypes = compatibleTypes(String.class, 
Float.class);
 
     protected FloatConverter(FieldAccessor fieldAccessor) {
       super(fieldAccessor);
@@ -528,7 +531,7 @@ public class FieldConverters {
    * for null values. Allows conversion from String and Float.
    */
   public static class DoubleConverter extends FieldConverter<Double> {
-    static Set<Class<?>> compatibleTypes = ImmutableSet.of(String.class, 
Float.class, Double.class);
+    static Set<Class<?>> compatibleTypes = compatibleTypes(String.class, 
Float.class, Double.class);
 
     protected DoubleConverter(FieldAccessor fieldAccessor) {
       super(fieldAccessor);
@@ -596,7 +599,7 @@ public class FieldConverters {
    */
   public static class StringConverter extends FieldConverter<String> {
     static Set<Class<?>> compatibleTypes =
-        ImmutableSet.of(
+        compatibleTypes(
             Integer.class,
             Long.class,
             Short.class,
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java 
b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
index 8a8a4f1b5..5ec94eaea 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
@@ -21,8 +21,6 @@ package org.apache.fory.type;
 
 import static org.apache.fory.util.Preconditions.checkArgument;
 
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
@@ -59,6 +57,8 @@ import org.apache.fory.annotation.Uint64ArrayType;
 import org.apache.fory.annotation.Uint64Type;
 import org.apache.fory.annotation.Uint8ArrayType;
 import org.apache.fory.annotation.Uint8Type;
+import org.apache.fory.collection.Cache;
+import org.apache.fory.collection.CacheBuilder;
 import org.apache.fory.collection.Collections;
 import org.apache.fory.collection.Tuple2;
 import org.apache.fory.memory.Platform;
diff --git 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
index 04906f123..2f67bfc3b 100644
--- 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
+++ 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
@@ -17,62 +17,18 @@
 
 # 
https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/Reflection/#unsafe-accesses
 :
 # The unsafe offset get on build time may be different from runtime
-Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
+Args=--initialize-at-build-time=org.apache.fory.collection.BiMap,\
+    org.apache.fory.collection.BiMap$Inverse,\
+    org.apache.fory.collection.CacheBuilder$LocalCache,\
+    org.apache.fory.collection.ReferenceConcurrentMap,\
+    org.apache.fory.collection.ReferenceConcurrentMap$LookupKey,\
+    org.apache.fory.collection.ReferenceConcurrentMap$SoftValueReference,\
+    org.apache.fory.collection.ReferenceConcurrentMap$WeakKeyReference,\
+    org.apache.fory.memory.MemoryBuffer,\
+    org.apache.fory.serializer.collection.GuavaCollectionSerializers,\
     org.apache.fory.serializer.struct.Fingerprint,\
     org.apache.fory.serializer.Float16Serializer,\
     org.apache.fory.serializer.PrimitiveSerializers$Float16Serializer,\
-    com.google.common.base.Equivalence$Equals,\
-    com.google.common.base.Equivalence$Identity,\
-    com.google.common.base.Equivalence,\
-    com.google.common.base.FinalizableReferenceQueue,\
-    com.google.common.base.Ticker$1,\
-    com.google.common.base.internal.Finalizer,\
-    com.google.common.cache.CacheBuilder$1,\
-    com.google.common.cache.CacheBuilder$2,\
-    com.google.common.cache.CacheBuilder$3,\
-    com.google.common.cache.CacheBuilder$NullListener,\
-    com.google.common.cache.CacheBuilder$OneWeigher,\
-    com.google.common.cache.LocalCache$1,\
-    com.google.common.cache.LocalCache$2,\
-    com.google.common.cache.LocalCache$EntryFactory$1,\
-    com.google.common.cache.LocalCache$EntryFactory$2,\
-    com.google.common.cache.LocalCache$EntryFactory$3,\
-    com.google.common.cache.LocalCache$EntryFactory$4,\
-    com.google.common.cache.LocalCache$EntryFactory$5,\
-    com.google.common.cache.LocalCache$EntryFactory$6,\
-    com.google.common.cache.LocalCache$EntryFactory$7,\
-    com.google.common.cache.LocalCache$EntryFactory$8,\
-    com.google.common.cache.LocalCache$EntrySet,\
-    com.google.common.cache.LocalCache$LocalManualCache,\
-    com.google.common.cache.LocalCache$Segment,\
-    com.google.common.cache.LocalCache$SoftValueReference,\
-    com.google.common.cache.LocalCache$Strength$1,\
-    com.google.common.cache.LocalCache$Strength$2,\
-    com.google.common.cache.LocalCache$Strength$3,\
-    com.google.common.cache.LocalCache$StrongEntry,\
-    com.google.common.cache.LocalCache$StrongValueReference,\
-    com.google.common.cache.LocalCache$WeakEntry,\
-    com.google.common.cache.LocalCache$WeakValueReference,\
-    com.google.common.cache.LocalCache,\
-    com.google.common.collect.AbstractIterator$1,\
-    com.google.common.collect.ImmutableSortedMap,\
-    com.google.common.collect.NaturalOrdering,\
-    com.google.common.collect.Platform,\
-    com.google.common.collect.RegularImmutableBiMap,\
-    com.google.common.collect.RegularImmutableList,\
-    com.google.common.collect.RegularImmutableMap,\
-    com.google.common.collect.RegularImmutableSet,\
-    com.google.common.collect.RegularImmutableSortedSet,\
-    com.google.common.collect.SingletonImmutableBiMap,\
-    com.google.common.collect.SingletonImmutableList,\
-    com.google.common.collect.SingletonImmutableSet,\
-    com.google.common.collect.HashBiMap,\
-    com.google.common.collect.HashBiMap$BiEntry,\
-    com.google.common.collect.HashBiMap$Inverse,\
-    com.google.common.math.IntMath$1,\
-    com.google.common.primitives.Primitives,\
-    com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper,\
-    com.google.common.util.concurrent.SettableFuture,\
     java.io.IOException,\
     java.lang.ArrayIndexOutOfBoundsException,\
     java.lang.Boolean,\
@@ -462,21 +418,6 @@ 
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
     
org.apache.fory.serializer.collection.CollectionSerializers$XlangSetDefaultSerializer,\
     
org.apache.fory.serializer.collection.ForyArrayAsListSerializer$ArrayAsList,\
     org.apache.fory.serializer.collection.ForyArrayAsListSerializer,\
-    org.apache.fory.serializer.collection.GuavaCollectionSerializers$1,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$1GuavaEmptyBiMap,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$1GuavaEmptySet,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$1GuavaEmptySortedMap,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$1GuavaEmptySortedSet,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$GuavaCollectionSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$GuavaMapSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$ImmutableBiMapSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$ImmutableListSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$ImmutableMapSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$ImmutableSetSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$ImmutableSortedMapSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$ImmutableSortedSetSerializer,\
-    
org.apache.fory.serializer.collection.GuavaCollectionSerializers$RegularImmutableListSerializer,\
-    org.apache.fory.serializer.collection.GuavaCollectionSerializers,\
     
org.apache.fory.serializer.collection.ImmutableCollectionSerializers$ImmutableListSerializer,\
     
org.apache.fory.serializer.collection.ImmutableCollectionSerializers$ImmutableMapSerializer,\
     
org.apache.fory.serializer.collection.ImmutableCollectionSerializers$ImmutableSetSerializer,\
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/GuavaOptionalDependencyTest.java 
b/java/fory-core/src/test/java/org/apache/fory/GuavaOptionalDependencyTest.java
new file mode 100644
index 000000000..0cda0ea87
--- /dev/null
+++ 
b/java/fory-core/src/test/java/org/apache/fory/GuavaOptionalDependencyTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import org.apache.fory.config.Language;
+import org.apache.fory.resolver.ClassResolver;
+import org.apache.fory.serializer.collection.GuavaCollectionSerializers;
+import org.testng.annotations.Test;
+
+public class GuavaOptionalDependencyTest {
+  private static final String RESULT_PREFIX = "RESULT:";
+
+  @Test
+  public void testBuildWithoutGuavaAndReserveIds() throws Exception {
+    assertTrue(GuavaCollectionSerializers.isGuavaAvailable());
+    RegistrationIds inProcessIds = currentProcessIds();
+    assertEquals(
+        inProcessIds.enabledId - inProcessIds.disabledId,
+        GuavaCollectionSerializers.getNumReservedTypeIds());
+    RegistrationIds childIds = runWithoutGuava();
+    assertEquals(childIds.enabledId, inProcessIds.enabledId);
+    assertEquals(childIds.disabledId, inProcessIds.disabledId);
+  }
+
+  private static RegistrationIds currentProcessIds() {
+    return new RegistrationIds(registeredInternalId(true), 
registeredInternalId(false));
+  }
+
+  private static int registeredInternalId(boolean registerGuavaTypes) {
+    Fory fory =
+        Fory.builder()
+            .withLanguage(Language.JAVA)
+            .registerGuavaTypes(registerGuavaTypes)
+            .requireClassRegistration(false)
+            .suppressClassRegistrationWarnings(true)
+            .build();
+    ClassResolver resolver = (ClassResolver) fory.getTypeResolver();
+    resolver.registerInternal(InternalSample.class);
+    return resolver.getRegisteredClassId(InternalSample.class);
+  }
+
+  private static RegistrationIds runWithoutGuava() throws Exception {
+    String javaBin =
+        System.getProperty("java.home") + File.separator + "bin" + 
File.separator + "java";
+    String filteredClassPath = 
removeGuavaFromClasspath(System.getProperty("java.class.path"));
+    Process process =
+        new ProcessBuilder(javaBin, "-cp", filteredClassPath, 
NoGuavaMain.class.getName())
+            .redirectErrorStream(true)
+            .start();
+    String output = readFully(process.getInputStream());
+    assertEquals(process.waitFor(), 0, output);
+    return parseResult(output);
+  }
+
+  private static RegistrationIds parseResult(String output) {
+    for (String line : output.split("\\R")) {
+      if (line.startsWith(RESULT_PREFIX)) {
+        String[] parts = line.substring(RESULT_PREFIX.length()).split(",");
+        return new RegistrationIds(Integer.parseInt(parts[0]), 
Integer.parseInt(parts[1]));
+      }
+    }
+    throw new AssertionError("Missing result line in output:\n" + output);
+  }
+
+  private static String removeGuavaFromClasspath(String classPath) {
+    return 
Arrays.stream(classPath.split(java.util.regex.Pattern.quote(File.pathSeparator)))
+        .filter(path -> !new File(path).getName().startsWith("guava-"))
+        .collect(Collectors.joining(File.pathSeparator));
+  }
+
+  private static String readFully(InputStream inputStream) throws IOException {
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    byte[] buffer = new byte[1024];
+    int read;
+    while ((read = inputStream.read(buffer)) != -1) {
+      outputStream.write(buffer, 0, read);
+    }
+    return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+  }
+
+  private static final class RegistrationIds {
+    private final int enabledId;
+    private final int disabledId;
+
+    private RegistrationIds(int enabledId, int disabledId) {
+      this.enabledId = enabledId;
+      this.disabledId = disabledId;
+    }
+  }
+
+  public static final class NoGuavaMain {
+    public static void main(String[] args) {
+      RegistrationIds ids = currentProcessIds();
+      Fory fory =
+          Fory.builder()
+              .withLanguage(Language.JAVA)
+              .registerGuavaTypes(true)
+              .requireClassRegistration(false)
+              .suppressClassRegistrationWarnings(true)
+              .build();
+      byte[] bytes = fory.serialize(new SampleValue("fory"));
+      SampleValue value = (SampleValue) fory.deserialize(bytes);
+      if (!"fory".equals(value.value)) {
+        throw new AssertionError("Unexpected round-trip value " + value.value);
+      }
+      System.out.println(RESULT_PREFIX + ids.enabledId + "," + ids.disabledId);
+    }
+  }
+
+  public static final class InternalSample {}
+
+  public static final class SampleValue {
+    private final String value;
+
+    public SampleValue(String value) {
+      this.value = value;
+    }
+  }
+}
diff --git a/java/fory-format/pom.xml b/java/fory-format/pom.xml
index 679ecdad0..9e89f5e26 100644
--- a/java/fory-format/pom.xml
+++ b/java/fory-format/pom.xml
@@ -90,6 +90,11 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/java/fory-testsuite/pom.xml b/java/fory-testsuite/pom.xml
index 0863dfe01..a743ecead 100644
--- a/java/fory-testsuite/pom.xml
+++ b/java/fory-testsuite/pom.xml
@@ -111,6 +111,11 @@
       <version>1.2.83</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/GuavaCollectionSerializersTest.java
 
b/java/fory-testsuite/src/test/java/org/apache/fory/serializer/collection/GuavaCollectionSerializersTest.java
similarity index 74%
rename from 
java/fory-core/src/test/java/org/apache/fory/serializer/collection/GuavaCollectionSerializersTest.java
rename to 
java/fory-testsuite/src/test/java/org/apache/fory/serializer/collection/GuavaCollectionSerializersTest.java
index a1fb62fe9..ff9458c07 100644
--- 
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/GuavaCollectionSerializersTest.java
+++ 
b/java/fory-testsuite/src/test/java/org/apache/fory/serializer/collection/GuavaCollectionSerializersTest.java
@@ -26,15 +26,57 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.ImmutableSortedSet;
 import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Data;
+import java.util.Objects;
 import org.apache.fory.Fory;
-import org.apache.fory.ForyTestBase;
+import org.apache.fory.TestBase;
 import org.apache.fory.config.Language;
 import org.testng.Assert;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
-public class GuavaCollectionSerializersTest extends ForyTestBase {
+public class GuavaCollectionSerializersTest extends TestBase {
+  @DataProvider(name = "trackingRefFory")
+  public static Object[][] trackingRefFory() {
+    return new Object[][] {{newJavaFory(true, false)}, {newJavaFory(false, 
false)}};
+  }
+
+  @DataProvider(name = "foryCopyConfig")
+  public static Object[][] foryCopyConfig() {
+    return new Object[][] {{newCopyFory(false)}, {newCopyFory(true)}};
+  }
+
+  @DataProvider(name = "javaFory")
+  public static Object[][] javaFory() {
+    return new Object[][] {
+      {newJavaFory(true, false)},
+      {newJavaFory(false, false)},
+      {newJavaFory(true, true)},
+      {newJavaFory(false, true)}
+    };
+  }
+
+  private static Fory newJavaFory(boolean trackingRef, boolean codegen) {
+    return builder()
+        .withRefTracking(trackingRef)
+        .withCodegen(codegen)
+        .suppressClassRegistrationWarnings(true)
+        .build();
+  }
+
+  private static Fory newCopyFory(boolean codegen) {
+    return builder()
+        .withRefCopy(true)
+        .withJdkClassSerializableCheck(false)
+        .withCodegen(codegen)
+        .suppressClassRegistrationWarnings(true)
+        .build();
+  }
+
+  private static void copyCheck(Fory fory, Object obj) {
+    Object copy = fory.copy(obj);
+    Assert.assertEquals(copy, obj);
+    Assert.assertNotSame(copy, obj);
+  }
 
   @Test(dataProvider = "trackingRefFory")
   public void testImmutableListSerializer(Fory fory) {
@@ -153,8 +195,13 @@ public class GuavaCollectionSerializersTest extends 
ForyTestBase {
   }
 
   @Test
-  public void tesXlangSerialize() {
-    Fory fory = Fory.builder().withLanguage(Language.XLANG).build();
+  public void testXlangSerialize() {
+    Fory fory =
+        Fory.builder()
+            .withLanguage(Language.XLANG)
+            .requireClassRegistration(false)
+            .suppressClassRegistrationWarnings(true)
+            .build();
     serDe(fory, ImmutableBiMap.of());
     serDe(fory, ImmutableBiMap.of(1, 2));
     serDe(fory, ImmutableBiMap.of(1, 2, 3, 4));
@@ -168,23 +215,45 @@ public class GuavaCollectionSerializersTest extends 
ForyTestBase {
     serDe(fory, ImmutableSet.of(1, 2, 3, 4));
   }
 
-  @Data
-  @AllArgsConstructor
-  public static class Pojo {
-    List<List<Object>> data;
-  }
-
   @Test(dataProvider = "javaFory")
-  void testNestedRefTracking(Fory fory) {
+  public void testNestedRefTracking(Fory fory) {
     Pojo pojo = new Pojo(ImmutableList.of(ImmutableList.of(1, 2), 
ImmutableList.of(2, 2)));
-    byte[] bytes = fory.serialize(pojo);
-    Pojo deserializedPojo = (Pojo) fory.deserialize(bytes);
-    System.out.println(deserializedPojo);
+    Assert.assertEquals(serDe(fory, pojo), pojo);
   }
 
   @Test(dataProvider = "foryCopyConfig")
-  void testNestedRefTrackingCopy(Fory fory) {
+  public void testNestedRefTrackingCopy(Fory fory) {
     Pojo pojo = new Pojo(ImmutableList.of(ImmutableList.of(1, 2), 
ImmutableList.of(2, 2)));
     copyCheck(fory, pojo);
   }
+
+  public static final class Pojo {
+    private final List<List<Object>> data;
+
+    public Pojo(List<List<Object>> data) {
+      this.data = data;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (!(other instanceof Pojo)) {
+        return false;
+      }
+      Pojo pojo = (Pojo) other;
+      return Objects.equals(data, pojo.data);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(data);
+    }
+
+    @Override
+    public String toString() {
+      return "Pojo{" + "data=" + data + '}';
+    }
+  }
 }
diff --git 
a/scala/src/main/java/org/apache/fory/serializer/scala/ScalaDispatcher.java 
b/scala/src/main/java/org/apache/fory/serializer/scala/ScalaDispatcher.java
index d4294d6dc..e2af2a2ef 100644
--- a/scala/src/main/java/org/apache/fory/serializer/scala/ScalaDispatcher.java
+++ b/scala/src/main/java/org/apache/fory/serializer/scala/ScalaDispatcher.java
@@ -19,11 +19,11 @@
 
 package org.apache.fory.serializer.scala;
 
-import com.google.common.base.Preconditions;
 import org.apache.fory.resolver.TypeResolver;
 import org.apache.fory.serializer.JavaSerializer;
 import org.apache.fory.serializer.Serializer;
 import org.apache.fory.serializer.SerializerFactory;
+import org.apache.fory.util.Preconditions;
 import scala.collection.generic.DefaultSerializable;
 
 import java.lang.reflect.Method;


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

Reply via email to