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 e2f62c7ae feat(java): add async codegen for Fory JSON (#3825)
e2f62c7ae is described below

commit e2f62c7ae439ef4e2fa1492bd6556a4bf81eda4a
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Jul 8 22:23:23 2026 +0530

    feat(java): add async codegen for Fory JSON (#3825)
    
    ## What changed
    
    - Added `ForyJsonBuilder.withAsyncCompilation(boolean)` with async
    compilation enabled by default for Fory JSON.
    - Added `JsonJITContext` to publish generated JSON object codecs using
    the core JIT callback model.
    - Removed the `ObjectCodecs` carrier and made generated codecs own their
    writer/reader delegates directly.
    - Updated resolver-owned object codec publication so async compilation
    replaces the single current codec without wrapper/factory/state
    abstractions.
    - Parameterized Fory JSON tests over codegen on/off with async disabled,
    and added dedicated async compilation coverage.
    
    ## Validation
    
    - `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json -DskipTests compile`
    - `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json -DskipTests
    test-compile`
    - `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json
    -Dtest=JsonAsyncCompilationTest,JsonGeneratedCodecTest test`
    - `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -pl fory-json test`
    - `mvn -pl fory-json spotless:apply`
    - `git diff --cached --check`
---
 .../java/org/apache/fory/json/ForyJsonBuilder.java |  16 +-
 .../main/java/org/apache/fory/json/JsonConfig.java |  64 +++-
 .../org/apache/fory/json/codec/ArrayCodec.java     |   9 +-
 .../apache/fory/json/codec/CollectionCodec.java    |   9 +-
 .../fory/json/codec/GeneratedObjectCodec.java      |  21 +-
 .../java/org/apache/fory/json/codec/MapCodec.java  |   3 +-
 .../org/apache/fory/json/codec/ObjectCodec.java    |  17 +-
 .../org/apache/fory/json/codec/ObjectCodecs.java   |  75 -----
 .../org/apache/fory/json/codec/ScalarCodecs.java   |   9 +-
 .../org/apache/fory/json/codegen/JsonCodegen.java  | 320 ++++++++++++------
 .../apache/fory/json/codegen/JsonJITContext.java   | 205 ++++++++++++
 .../fory/json/resolver/JsonSharedRegistry.java     |  54 +++-
 .../apache/fory/json/resolver/JsonTypeInfo.java    |   8 +-
 .../fory/json/resolver/JsonTypeResolver.java       | 199 +++++++++++-
 .../org/apache/fory/json/ForyJsonTestModels.java   |  27 +-
 .../apache/fory/json/JsonAsyncCompilationTest.java | 360 +++++++++++++++++++++
 .../org/apache/fory/json/JsonContainerTest.java    |  64 ++--
 .../java/org/apache/fory/json/JsonDepthTest.java   |  20 +-
 .../apache/fory/json/JsonGeneratedCodecTest.java   |  51 ++-
 .../fory/json/JsonGuavaOptionalDependencyTest.java |  18 +-
 .../java/org/apache/fory/json/JsonObjectTest.java  |  38 ++-
 .../org/apache/fory/json/JsonPropertyTest.java     |  28 +-
 .../java/org/apache/fory/json/JsonRecordTest.java  |   8 +-
 .../java/org/apache/fory/json/JsonScalarTest.java  |  88 ++---
 .../java/org/apache/fory/json/JsonStringTest.java  |  40 ++-
 25 files changed, 1394 insertions(+), 357 deletions(-)

diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java 
b/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
index 148c24edd..a3795048a 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/ForyJsonBuilder.java
@@ -26,6 +26,7 @@ import org.apache.fory.json.resolver.CodecRegistry;
 public final class ForyJsonBuilder {
   private boolean writeNullFields;
   private boolean codegenEnabled = true;
+  private boolean asyncCompilationEnabled = true;
   private boolean propertyDiscoveryEnabled = true;
   private int maxDepth = ForyJson.DEFAULT_MAX_DEPTH;
   private final CodecRegistry codecRegistry = new CodecRegistry();
@@ -38,12 +39,18 @@ public final class ForyJsonBuilder {
     return this;
   }
 
-  /** Enables runtime-generated writers for supported public-field classes. */
+  /** Enables generated object codecs for supported classes. Enabled by 
default. */
   public ForyJsonBuilder withCodegen(boolean codegenEnabled) {
     this.codegenEnabled = codegenEnabled;
     return this;
   }
 
+  /** Enables asynchronous runtime compilation for generated object codecs. 
Enabled by default. */
+  public ForyJsonBuilder withAsyncCompilation(boolean asyncCompilationEnabled) 
{
+    this.asyncCompilationEnabled = asyncCompilationEnabled;
+    return this;
+  }
+
   /**
    * Enables field mode, where JSON object members are discovered from Java 
fields only. When
    * disabled, Fory JSON uses the default JavaBean property model: public 
getters, public setters,
@@ -72,6 +79,11 @@ public final class ForyJsonBuilder {
   public ForyJson build() {
     return new ForyJson(
         new JsonConfig(
-            writeNullFields, codegenEnabled, propertyDiscoveryEnabled, 
maxDepth, codecRegistry));
+            writeNullFields,
+            codegenEnabled,
+            asyncCompilationEnabled,
+            propertyDiscoveryEnabled,
+            maxDepth,
+            codecRegistry));
   }
 }
diff --git a/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java 
b/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
index 3cda718a4..a725db01f 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/JsonConfig.java
@@ -29,24 +29,29 @@ import org.apache.fory.json.resolver.CodecRegistry;
 public final class JsonConfig {
   private final boolean writeNullFields;
   private final boolean codegenEnabled;
+  private final boolean asyncCompilationEnabled;
   private final boolean propertyDiscoveryEnabled;
   private final int maxDepth;
   private final CodecRegistry codecRegistry;
   private final String codecRegistryKey;
-  private transient int configHash;
+  private final CodegenKey codegenKey;
+  private transient int codegenHash;
 
   JsonConfig(
       boolean writeNullFields,
       boolean codegenEnabled,
+      boolean asyncCompilationEnabled,
       boolean propertyDiscoveryEnabled,
       int maxDepth,
       CodecRegistry codecRegistry) {
     this.writeNullFields = writeNullFields;
     this.codegenEnabled = codegenEnabled;
+    this.asyncCompilationEnabled = asyncCompilationEnabled;
     this.propertyDiscoveryEnabled = propertyDiscoveryEnabled;
     this.maxDepth = maxDepth;
     this.codecRegistry = codecRegistry;
     codecRegistryKey = codecRegistry.codegenKey();
+    codegenKey = new CodegenKey(writeNullFields, propertyDiscoveryEnabled, 
codecRegistryKey);
   }
 
   public boolean writeNullFields() {
@@ -57,6 +62,10 @@ public final class JsonConfig {
     return codegenEnabled;
   }
 
+  public boolean asyncCompilationEnabled() {
+    return asyncCompilationEnabled;
+  }
+
   public boolean propertyDiscoveryEnabled() {
     return propertyDiscoveryEnabled;
   }
@@ -80,6 +89,7 @@ public final class JsonConfig {
     JsonConfig that = (JsonConfig) other;
     return writeNullFields == that.writeNullFields
         && codegenEnabled == that.codegenEnabled
+        && asyncCompilationEnabled == that.asyncCompilationEnabled
         && propertyDiscoveryEnabled == that.propertyDiscoveryEnabled
         && maxDepth == that.maxDepth
         && Objects.equals(codecRegistryKey, that.codecRegistryKey);
@@ -88,18 +98,56 @@ public final class JsonConfig {
   @Override
   public int hashCode() {
     return Objects.hash(
-        writeNullFields, codegenEnabled, propertyDiscoveryEnabled, maxDepth, 
codecRegistryKey);
+        writeNullFields,
+        codegenEnabled,
+        asyncCompilationEnabled,
+        propertyDiscoveryEnabled,
+        maxDepth,
+        codecRegistryKey);
   }
 
   private static final AtomicInteger COUNTER = new AtomicInteger(0);
 
-  // Equal configs share one map entry, following core Config's generated-code 
naming model.
-  private static final ConcurrentMap<JsonConfig, Integer> CONFIG_ID_MAP = new 
ConcurrentHashMap<>();
+  // Equal generated source inputs share one map entry, following core 
generated-code naming model.
+  private static final ConcurrentMap<CodegenKey, Integer> CODEGEN_ID_MAP =
+      new ConcurrentHashMap<>();
+
+  public int getCodegenHash() {
+    if (codegenHash == 0) {
+      codegenHash = CODEGEN_ID_MAP.computeIfAbsent(codegenKey, key -> 
COUNTER.incrementAndGet());
+    }
+    return codegenHash;
+  }
+
+  private static final class CodegenKey {
+    private final boolean writeNullFields;
+    private final boolean propertyDiscoveryEnabled;
+    private final String codecRegistryKey;
+
+    private CodegenKey(
+        boolean writeNullFields, boolean propertyDiscoveryEnabled, String 
codecRegistryKey) {
+      this.writeNullFields = writeNullFields;
+      this.propertyDiscoveryEnabled = propertyDiscoveryEnabled;
+      this.codecRegistryKey = codecRegistryKey;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (other == null || getClass() != other.getClass()) {
+        return false;
+      }
+      CodegenKey that = (CodegenKey) other;
+      return writeNullFields == that.writeNullFields
+          && propertyDiscoveryEnabled == that.propertyDiscoveryEnabled
+          && Objects.equals(codecRegistryKey, that.codecRegistryKey);
+    }
 
-  public int getConfigHash() {
-    if (configHash == 0) {
-      configHash = CONFIG_ID_MAP.computeIfAbsent(this, key -> 
COUNTER.incrementAndGet());
+    @Override
+    public int hashCode() {
+      return Objects.hash(writeNullFields, propertyDiscoveryEnabled, 
codecRegistryKey);
     }
-    return configHash;
   }
 }
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
index d45e014d2..563e62c61 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ArrayCodec.java
@@ -75,7 +75,8 @@ public abstract class ArrayCodec extends AbstractJsonCodec {
     } else if (componentType == String.class) {
       return StringArrayCodec.INSTANCE;
     }
-    return new ObjectArrayCodec(componentType, 
resolver.getTypeInfo(componentType, componentType));
+    return new ObjectArrayCodec(
+        componentType, resolver.getTypeInfo(componentType, componentType), 
resolver);
   }
 
   @Override
@@ -1231,15 +1232,17 @@ public abstract class ArrayCodec extends 
AbstractJsonCodec {
     private static final int MAX_CACHED_VALUES_SIZE = 1024;
 
     private final JsonTypeInfo elementTypeInfo;
-    private final JsonCodec elementCodec;
+    private JsonCodec elementCodec;
     // Recursive object-array reads borrow one scratch slot per active depth.
     private final Object[][] valuesCache = new Object[VALUES_CACHE_DEPTH][];
     private int valuesDepth;
 
-    private ObjectArrayCodec(Class<?> componentType, JsonTypeInfo 
elementTypeInfo) {
+    private ObjectArrayCodec(
+        Class<?> componentType, JsonTypeInfo elementTypeInfo, JsonTypeResolver 
resolver) {
       super(componentType);
       this.elementTypeInfo = elementTypeInfo;
       elementCodec = elementTypeInfo.codec();
+      resolver.registerJITNotifyCallback(elementCodec, codec -> elementCodec = 
codec);
     }
 
     @Override
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
index c1a68c244..ace05e65c 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/CollectionCodec.java
@@ -109,7 +109,7 @@ public abstract class CollectionCodec extends 
AbstractJsonCodec {
     JsonCodec elementCodec = elementTypeInfo.codec();
     if (elementCodec instanceof BaseObjectCodec) {
       return new ObjectCollectionCodec(
-          typeRef, factory, elementTypeInfo, (BaseObjectCodec) elementCodec);
+          typeRef, factory, elementTypeInfo, (BaseObjectCodec) elementCodec, 
resolver);
     }
     return new GenericCollectionCodec(typeRef, factory, elementTypeInfo, 
elementCodec);
   }
@@ -877,16 +877,19 @@ public abstract class CollectionCodec extends 
AbstractJsonCodec {
 
   public static final class ObjectCollectionCodec extends CollectionCodec {
     private final JsonTypeInfo elementTypeInfo;
-    private final BaseObjectCodec elementCodec;
+    private BaseObjectCodec elementCodec;
 
     private ObjectCollectionCodec(
         TypeRef<?> typeRef,
         CollectionFactory factory,
         JsonTypeInfo elementTypeInfo,
-        BaseObjectCodec elementCodec) {
+        BaseObjectCodec elementCodec,
+        JsonTypeResolver resolver) {
       super(typeRef, factory);
       this.elementTypeInfo = elementTypeInfo;
       this.elementCodec = elementCodec;
+      resolver.registerJITNotifyCallback(
+          elementCodec, codec -> this.elementCodec = (BaseObjectCodec) codec);
     }
 
     @Override
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
index fa4dc59b3..dd5c1a984 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/GeneratedObjectCodec.java
@@ -42,14 +42,21 @@ public final class GeneratedObjectCodec extends 
BaseObjectCodec {
   private final Utf16ObjectReader utf16Reader;
   private final Utf8ObjectReader utf8Reader;
 
-  GeneratedObjectCodec(ObjectCodec base, ObjectCodecs codecs) {
+  GeneratedObjectCodec(
+      ObjectCodec base,
+      StringObjectWriter stringWriter,
+      Utf8ObjectWriter utf8Writer,
+      ObjectReader reader,
+      Latin1ObjectReader latin1Reader,
+      Utf16ObjectReader utf16Reader,
+      Utf8ObjectReader utf8Reader) {
     super(base.type, base.writeFields, base.readFields, base.instantiator);
-    stringWriter = codecs.stringWriter();
-    utf8Writer = codecs.utf8Writer();
-    reader = codecs.reader();
-    latin1Reader = codecs.latin1Reader();
-    utf16Reader = codecs.utf16Reader();
-    utf8Reader = codecs.utf8Reader();
+    this.stringWriter = stringWriter;
+    this.utf8Writer = utf8Writer;
+    this.reader = reader;
+    this.latin1Reader = latin1Reader;
+    this.utf16Reader = utf16Reader;
+    this.utf8Reader = utf8Reader;
   }
 
   @Override
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
index c7899db14..680fc12e0 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/MapCodec.java
@@ -231,7 +231,7 @@ public abstract class MapCodec extends AbstractJsonCodec {
   public static final class GenericMapCodec extends MapCodec {
     private final MapKeyCodec keyCodec;
     private final JsonTypeInfo valueTypeInfo;
-    private final JsonCodec valueCodec;
+    private JsonCodec valueCodec;
 
     private GenericMapCodec(
         TypeRef<?> typeRef,
@@ -244,6 +244,7 @@ public abstract class MapCodec extends AbstractJsonCodec {
       this.keyCodec = keyCodec;
       valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
       valueCodec = valueTypeInfo.codec();
+      resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec = 
codec);
     }
 
     @Override
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java
index 225bb2929..7fcae9be5 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodec.java
@@ -20,6 +20,12 @@
 package org.apache.fory.json.codec;
 
 import org.apache.fory.json.meta.JsonFieldInfo;
+import org.apache.fory.json.reader.Latin1ObjectReader;
+import org.apache.fory.json.reader.ObjectReader;
+import org.apache.fory.json.reader.Utf16ObjectReader;
+import org.apache.fory.json.reader.Utf8ObjectReader;
+import org.apache.fory.json.writer.StringObjectWriter;
+import org.apache.fory.json.writer.Utf8ObjectWriter;
 import org.apache.fory.reflect.ObjectInstantiator;
 
 public final class ObjectCodec extends BaseObjectCodec {
@@ -31,7 +37,14 @@ public final class ObjectCodec extends BaseObjectCodec {
     super(type, writeFields, readFields, instantiator);
   }
 
-  public GeneratedObjectCodec withCodecs(ObjectCodecs codecs) {
-    return new GeneratedObjectCodec(this, codecs);
+  public GeneratedObjectCodec withGenerated(
+      StringObjectWriter stringWriter,
+      Utf8ObjectWriter utf8Writer,
+      ObjectReader reader,
+      Latin1ObjectReader latin1Reader,
+      Utf16ObjectReader utf16Reader,
+      Utf8ObjectReader utf8Reader) {
+    return new GeneratedObjectCodec(
+        this, stringWriter, utf8Writer, reader, latin1Reader, utf16Reader, 
utf8Reader);
   }
 }
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodecs.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodecs.java
deleted file mode 100644
index c18719f4f..000000000
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ObjectCodecs.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.json.codec;
-
-import org.apache.fory.json.reader.Latin1ObjectReader;
-import org.apache.fory.json.reader.ObjectReader;
-import org.apache.fory.json.reader.Utf16ObjectReader;
-import org.apache.fory.json.reader.Utf8ObjectReader;
-import org.apache.fory.json.writer.StringObjectWriter;
-import org.apache.fory.json.writer.Utf8ObjectWriter;
-
-public final class ObjectCodecs {
-  private final StringObjectWriter stringWriter;
-  private final Utf8ObjectWriter utf8Writer;
-  private final ObjectReader reader;
-  private final Latin1ObjectReader latin1Reader;
-  private final Utf16ObjectReader utf16Reader;
-  private final Utf8ObjectReader utf8Reader;
-
-  public ObjectCodecs(
-      StringObjectWriter stringWriter,
-      Utf8ObjectWriter utf8Writer,
-      ObjectReader reader,
-      Latin1ObjectReader latin1Reader,
-      Utf16ObjectReader utf16Reader,
-      Utf8ObjectReader utf8Reader) {
-    this.stringWriter = stringWriter;
-    this.utf8Writer = utf8Writer;
-    this.reader = reader;
-    this.latin1Reader = latin1Reader;
-    this.utf16Reader = utf16Reader;
-    this.utf8Reader = utf8Reader;
-  }
-
-  public StringObjectWriter stringWriter() {
-    return stringWriter;
-  }
-
-  public Utf8ObjectWriter utf8Writer() {
-    return utf8Writer;
-  }
-
-  public ObjectReader reader() {
-    return reader;
-  }
-
-  public Latin1ObjectReader latin1Reader() {
-    return latin1Reader;
-  }
-
-  public Utf16ObjectReader utf16Reader() {
-    return utf16Reader;
-  }
-
-  public Utf8ObjectReader utf8Reader() {
-    return utf8Reader;
-  }
-}
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
index 389e538db..e5d51c497 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codec/ScalarCodecs.java
@@ -2054,12 +2054,13 @@ public final class ScalarCodecs {
 
   public static final class AtomicReferenceCodec extends AbstractJsonCodec {
     private final JsonTypeInfo valueTypeInfo;
-    private final JsonCodec valueCodec;
+    private JsonCodec valueCodec;
 
     public AtomicReferenceCodec(java.lang.reflect.Type valueType, 
JsonTypeResolver resolver) {
       Class<?> valueRawType = CodecUtils.rawType(valueType, Object.class);
       valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
       valueCodec = valueTypeInfo.codec();
+      resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec = 
codec);
     }
 
     @Override
@@ -2189,12 +2190,13 @@ public final class ScalarCodecs {
 
   public static final class AtomicReferenceArrayCodec extends 
AbstractJsonCodec {
     private final JsonTypeInfo valueTypeInfo;
-    private final JsonCodec valueCodec;
+    private JsonCodec valueCodec;
 
     public AtomicReferenceArrayCodec(java.lang.reflect.Type valueType, 
JsonTypeResolver resolver) {
       Class<?> valueRawType = CodecUtils.rawType(valueType, Object.class);
       valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
       valueCodec = valueTypeInfo.codec();
+      resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec = 
codec);
     }
 
     @Override
@@ -2243,12 +2245,13 @@ public final class ScalarCodecs {
 
   public static final class OptionalCodec extends AbstractJsonCodec {
     private final JsonTypeInfo valueTypeInfo;
-    private final JsonCodec valueCodec;
+    private JsonCodec valueCodec;
 
     public OptionalCodec(java.lang.reflect.Type valueType, JsonTypeResolver 
resolver) {
       Class<?> valueRawType = CodecUtils.rawType(valueType, Object.class);
       valueTypeInfo = resolver.getTypeInfo(valueType, valueRawType);
       valueCodec = valueTypeInfo.codec();
+      resolver.registerJITNotifyCallback(valueCodec, codec -> valueCodec = 
codec);
     }
 
     @Override
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java
index 5480f5cdf..28a72a1ca 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonCodegen.java
@@ -28,12 +28,14 @@ import java.lang.reflect.Modifier;
 import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import org.apache.fory.annotation.Internal;
 import org.apache.fory.codegen.CodeGenerator;
 import org.apache.fory.codegen.CompileUnit;
 import org.apache.fory.json.ForyJsonException;
 import org.apache.fory.json.codec.BaseObjectCodec;
+import org.apache.fory.json.codec.GeneratedObjectCodec;
 import org.apache.fory.json.codec.JsonCodec;
-import org.apache.fory.json.codec.ObjectCodecs;
+import org.apache.fory.json.codec.ObjectCodec;
 import org.apache.fory.json.meta.JsonFieldInfo;
 import org.apache.fory.json.meta.JsonFieldKind;
 import org.apache.fory.json.reader.Latin1ObjectReader;
@@ -45,6 +47,7 @@ import org.apache.fory.json.writer.StringObjectWriter;
 import org.apache.fory.json.writer.Utf8ObjectWriter;
 import org.apache.fory.platform.AndroidSupport;
 import org.apache.fory.platform.internal._JDKAccess;
+import org.apache.fory.reflect.ReflectionUtils;
 import org.apache.fory.util.record.RecordUtils;
 
 public final class JsonCodegen {
@@ -56,77 +59,54 @@ public final class JsonCodegen {
   private static final Map<String, Map<String, Integer>> ID_GENERATOR = new 
ConcurrentHashMap<>();
 
   final boolean writeNullFields;
-  private final int configHash;
+  private final int codegenHash;
   private final CodeGenerator codeGenerator;
   private final ClassLoader jsonLoader;
+  private final ConcurrentHashMap<Class<?>, GeneratedObjectCodecClasses> 
generatedClasses;
 
-  public JsonCodegen(boolean writeNullFields, int configHash) {
+  public JsonCodegen(boolean writeNullFields, int codegenHash) {
     this.writeNullFields = writeNullFields;
-    this.configHash = configHash;
+    this.codegenHash = codegenHash;
     jsonLoader = JsonCodegen.class.getClassLoader();
     codeGenerator = new CodeGenerator(jsonLoader);
+    generatedClasses = new ConcurrentHashMap<>();
   }
 
-  public ObjectCodecs compile(BaseObjectCodec objectCodec, JsonTypeResolver 
typeResolver) {
+  public GeneratedObjectCodecClasses generatedClasses(Class<?> type) {
+    return generatedClasses.get(type);
+  }
+
+  public GeneratedObjectCodecClasses compileClasses(ObjectCodec objectCodec) {
     Class<?> type = objectCodec.type();
-    if (!canCompile(type)) {
+    if (!canCompile(objectCodec)) {
       return null;
     }
-    boolean record = objectCodec.isRecord();
-    JsonFieldInfo[] writeProperties = objectCodec.writeFields();
-    for (int i = 0; i < writeProperties.length; i++) {
-      if (!canCompileWrite(writeProperties[i])) {
-        return null;
-      }
-    }
-    JsonFieldInfo[] readProperties = objectCodec.readFields();
-    for (int i = 0; i < readProperties.length; i++) {
-      if (!canCompileRead(readProperties[i], record)) {
-        return null;
-      }
+    GeneratedObjectCodecClasses classes = generatedClasses.get(type);
+    if (classes != null) {
+      return classes;
     }
-    String generatedPackage = CodeGenerator.getPackage(type);
-    JsonCodec[] writeCodecs = writeCodecs(writeProperties);
-    Utf8ObjectWriter utf8Writer =
-        (Utf8ObjectWriter)
-            compileWriter(
-                generatedPackage,
-                className(type, "Utf8"),
-                type,
-                writeProperties,
-                writeCodecs,
-                true);
-    if (utf8Writer == null) {
-      return null;
-    }
-    StringObjectWriter stringWriter =
-        (StringObjectWriter)
-            compileWriter(
-                generatedPackage,
-                className(type, "String"),
-                type,
-                writeProperties,
-                writeCodecs,
-                false);
-    if (stringWriter == null) {
+    return generatedClasses.computeIfAbsent(type, ignored -> 
buildClasses(objectCodec));
+  }
+
+  public GeneratedObjectCodec newCodec(
+      ObjectCodec objectCodec, JsonTypeResolver typeResolver, 
GeneratedObjectCodecClasses classes) {
+    if (classes == null) {
       return null;
     }
+    Class<?> type = objectCodec.type();
+    JsonFieldInfo[] writeProperties = objectCodec.writeFields();
+    JsonCodec[] writeCodecs = writeCodecs(writeProperties);
+    Utf8ObjectWriter utf8Writer = classes.newUtf8Writer(writeProperties, 
writeCodecs);
+    StringObjectWriter stringWriter = classes.newStringWriter(writeProperties, 
writeCodecs);
+    JsonFieldInfo[] readProperties = objectCodec.readFields();
     JsonCodec[] readCodecs = readCodecs(readProperties);
     BaseObjectCodec[] readObjectCodecs = readObjectCodecs(objectCodec, 
typeResolver);
-    ObjectReader reader =
-        (ObjectReader)
-            compileReader(
-                generatedPackage,
-                className(type, "Reader"),
-                type,
-                readProperties,
-                readCodecs,
-                readObjectCodecs,
-                record);
-    if (reader == null) {
-      return null;
-    }
-    return new ObjectCodecs(
+    ObjectReader reader = classes.newReader(readProperties, readCodecs, 
readObjectCodecs);
+    registerWriterCallbacks(typeResolver, stringWriter, writeProperties, 
writeCodecs);
+    registerWriterCallbacks(typeResolver, utf8Writer, writeProperties, 
writeCodecs);
+    registerReaderCallbacks(
+        typeResolver, reader, type, readProperties, readCodecs, 
readObjectCodecs);
+    return objectCodec.withGenerated(
         stringWriter,
         utf8Writer,
         reader,
@@ -135,67 +115,73 @@ public final class JsonCodegen {
         (Utf8ObjectReader) reader);
   }
 
-  private Object compileWriter(
+  private GeneratedObjectCodecClasses buildClasses(ObjectCodec objectCodec) {
+    Class<?> type = objectCodec.type();
+    boolean record = objectCodec.isRecord();
+    JsonFieldInfo[] writeProperties = objectCodec.writeFields();
+    JsonFieldInfo[] readProperties = objectCodec.readFields();
+    String generatedPackage = CodeGenerator.getPackage(type);
+    Class<?> utf8WriterClass =
+        compileWriterClass(generatedPackage, className(type, "Utf8"), type, 
writeProperties, true);
+    Class<?> stringWriterClass =
+        compileWriterClass(
+            generatedPackage, className(type, "String"), type, 
writeProperties, false);
+    Class<?> readerClass =
+        compileReaderClass(
+            generatedPackage, className(type, "Reader"), type, readProperties, 
record);
+    return new GeneratedObjectCodecClasses(stringWriterClass, utf8WriterClass, 
readerClass);
+  }
+
+  public boolean canCompile(BaseObjectCodec objectCodec) {
+    Class<?> type = objectCodec.type();
+    if (!canCompileType(type)) {
+      return false;
+    }
+    boolean record = objectCodec.isRecord();
+    JsonFieldInfo[] writeProperties = objectCodec.writeFields();
+    for (int i = 0; i < writeProperties.length; i++) {
+      if (!canCompileWrite(writeProperties[i])) {
+        return false;
+      }
+    }
+    JsonFieldInfo[] readProperties = objectCodec.readFields();
+    for (int i = 0; i < readProperties.length; i++) {
+      if (!canCompileRead(readProperties[i], record)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private Class<?> compileWriterClass(
       String generatedPackage,
       String className,
       Class<?> type,
       JsonFieldInfo[] properties,
-      JsonCodec[] nestedCodecs,
       boolean utf8) {
     String code =
         new JsonGeneratedCodecBuilder(
                 this, generatedPackage, className, type, properties, utf8, 
true, false)
             .genCode();
     try {
-      Class<?> writerClass = compileClass(generatedPackage, className, code);
-      if (AndroidSupport.IS_ANDROID) {
-        Constructor<?> constructor =
-            writerClass.getDeclaredConstructor(JsonFieldInfo[].class, 
JsonCodec[].class);
-        constructor.setAccessible(true);
-        return constructor.newInstance(properties, nestedCodecs);
-      }
-      MethodHandle constructor =
-          _JDKAccess._trustedLookup(writerClass)
-              .findConstructor(
-                  writerClass,
-                  MethodType.methodType(void.class, JsonFieldInfo[].class, 
JsonCodec[].class));
-      return constructor.invoke(properties, nestedCodecs);
+      return compileClass(generatedPackage, className, code);
     } catch (Throwable e) {
       throw new ForyJsonException("Cannot compile generated JSON writer " + 
className, e);
     }
   }
 
-  private Object compileReader(
+  private Class<?> compileReaderClass(
       String generatedPackage,
       String className,
       Class<?> type,
       JsonFieldInfo[] properties,
-      JsonCodec[] readCodecs,
-      BaseObjectCodec[] nestedCodecs,
       boolean record) {
     String code =
         new JsonGeneratedCodecBuilder(
                 this, generatedPackage, className, type, properties, false, 
false, record)
             .genCode();
     try {
-      Class<?> readerClass = compileClass(generatedPackage, className, code);
-      if (AndroidSupport.IS_ANDROID) {
-        Constructor<?> constructor =
-            readerClass.getDeclaredConstructor(
-                JsonFieldInfo[].class, JsonCodec[].class, 
BaseObjectCodec[].class);
-        constructor.setAccessible(true);
-        return constructor.newInstance(properties, readCodecs, nestedCodecs);
-      }
-      MethodHandle constructor =
-          _JDKAccess._trustedLookup(readerClass)
-              .findConstructor(
-                  readerClass,
-                  MethodType.methodType(
-                      void.class,
-                      JsonFieldInfo[].class,
-                      JsonCodec[].class,
-                      BaseObjectCodec[].class));
-      return constructor.invoke(properties, readCodecs, nestedCodecs);
+      return compileClass(generatedPackage, className, code);
     } catch (Throwable e) {
       throw new ForyJsonException("Cannot compile generated JSON reader " + 
className, e);
     }
@@ -287,7 +273,7 @@ public final class JsonCodegen {
     return !isPojo(elementType) || isVisible(elementType);
   }
 
-  private boolean canCompile(Class<?> type) {
+  private boolean canCompileType(Class<?> type) {
     return CodeGenerator.sourcePublicAccessible(type) && isVisible(type);
   }
 
@@ -315,6 +301,9 @@ public final class JsonCodegen {
   }
 
   Class<?> codecFieldType(JsonCodec codec) {
+    if (codec instanceof BaseObjectCodec) {
+      return BaseObjectCodec.class;
+    }
     Class<?> type = codec.getClass();
     if (isPublicSourceType(type) && isVisible(type)) {
       return type;
@@ -322,6 +311,43 @@ public final class JsonCodegen {
     return JsonCodec.class;
   }
 
+  private static void registerWriterCallbacks(
+      JsonTypeResolver resolver, Object writer, JsonFieldInfo[] properties, 
JsonCodec[] codecs) {
+    for (int i = 0; i < properties.length; i++) {
+      JsonFieldInfo property = properties[i];
+      JsonCodec codec = codecs[i];
+      if (usesWriteCodec(property) && codec instanceof BaseObjectCodec) {
+        registerFieldCallback(resolver, writer, "c" + i, codec);
+      }
+    }
+  }
+
+  private static void registerReaderCallbacks(
+      JsonTypeResolver resolver,
+      Object reader,
+      Class<?> type,
+      JsonFieldInfo[] properties,
+      JsonCodec[] codecs,
+      BaseObjectCodec[] objectCodecs) {
+    for (int i = 0; i < properties.length; i++) {
+      JsonFieldInfo property = properties[i];
+      JsonCodec codec = codecs[i];
+      if (usesReadCodec(property) && codec instanceof BaseObjectCodec) {
+        registerFieldCallback(resolver, reader, "r" + i, codec);
+      }
+      if (storesReadObjectCodec(type, property)) {
+        registerFieldCallback(resolver, reader, "c" + i, objectCodecs[i]);
+      }
+    }
+  }
+
+  private static void registerFieldCallback(
+      JsonTypeResolver resolver, Object owner, String fieldName, JsonCodec 
currentCodec) {
+    Field field = ReflectionUtils.getField(owner.getClass(), fieldName);
+    resolver.registerJITNotifyCallback(
+        currentCodec, codec -> ReflectionUtils.setObjectFieldValue(owner, 
field, codec));
+  }
+
   private static boolean isPublicSourceType(Class<?> type) {
     if (!CodeGenerator.sourcePublicAccessible(type)) {
       return false;
@@ -389,7 +415,7 @@ public final class JsonCodegen {
     String name = simpleClassName(type) + role + "ForyJsonCodec";
     Map<String, Integer> subGenerator =
         ID_GENERATOR.computeIfAbsent(name, key -> new ConcurrentHashMap<>());
-    String key = configHash + "_" + CodeGenerator.getClassUniqueId(type);
+    String key = codegenHash + "_" + CodeGenerator.getClassUniqueId(type);
     Integer id = subGenerator.get(key);
     if (id == null) {
       synchronized (subGenerator) {
@@ -443,4 +469,110 @@ public final class JsonCodegen {
     Field field = property.writeField();
     return field != null && RecordUtils.isRecord(field.getDeclaringClass());
   }
+
+  @Internal
+  public static final class GeneratedObjectCodecClasses {
+    private final MethodHandle stringWriterConstructor;
+    private final MethodHandle utf8WriterConstructor;
+    private final MethodHandle readerConstructor;
+    private final Constructor<?> androidStringWriterConstructor;
+    private final Constructor<?> androidUtf8WriterConstructor;
+    private final Constructor<?> androidReaderConstructor;
+
+    private GeneratedObjectCodecClasses(
+        Class<?> stringWriterClass, Class<?> utf8WriterClass, Class<?> 
readerClass) {
+      try {
+        if (AndroidSupport.IS_ANDROID) {
+          androidStringWriterConstructor = 
writerConstructor(stringWriterClass);
+          androidUtf8WriterConstructor = writerConstructor(utf8WriterClass);
+          androidReaderConstructor = readerConstructor(readerClass);
+          stringWriterConstructor = null;
+          utf8WriterConstructor = null;
+          readerConstructor = null;
+        } else {
+          stringWriterConstructor = writerHandle(stringWriterClass);
+          utf8WriterConstructor = writerHandle(utf8WriterClass);
+          readerConstructor = readerHandle(readerClass);
+          androidStringWriterConstructor = null;
+          androidUtf8WriterConstructor = null;
+          androidReaderConstructor = null;
+        }
+      } catch (Throwable e) {
+        throw new ForyJsonException(
+            "Cannot resolve generated JSON codec constructors for " + 
readerClass.getName(), e);
+      }
+    }
+
+    private StringObjectWriter newStringWriter(JsonFieldInfo[] properties, 
JsonCodec[] codecs) {
+      return (StringObjectWriter)
+          newWriter(androidStringWriterConstructor, stringWriterConstructor, 
properties, codecs);
+    }
+
+    private Utf8ObjectWriter newUtf8Writer(JsonFieldInfo[] properties, 
JsonCodec[] codecs) {
+      return (Utf8ObjectWriter)
+          newWriter(androidUtf8WriterConstructor, utf8WriterConstructor, 
properties, codecs);
+    }
+
+    private ObjectReader newReader(
+        JsonFieldInfo[] properties, JsonCodec[] codecs, BaseObjectCodec[] 
objectCodecs) {
+      try {
+        if (AndroidSupport.IS_ANDROID) {
+          return (ObjectReader)
+              androidReaderConstructor.newInstance(properties, codecs, 
objectCodecs);
+        }
+        return (ObjectReader) readerConstructor.invoke(properties, codecs, 
objectCodecs);
+      } catch (Throwable e) {
+        throw new ForyJsonException("Cannot instantiate generated JSON 
reader", e);
+      }
+    }
+
+    private static Object newWriter(
+        Constructor<?> androidConstructor,
+        MethodHandle constructor,
+        JsonFieldInfo[] properties,
+        JsonCodec[] codecs) {
+      try {
+        if (AndroidSupport.IS_ANDROID) {
+          return androidConstructor.newInstance(properties, codecs);
+        }
+        return constructor.invoke(properties, codecs);
+      } catch (Throwable e) {
+        throw new ForyJsonException("Cannot instantiate generated JSON 
writer", e);
+      }
+    }
+
+    private static Constructor<?> writerConstructor(Class<?> writerClass)
+        throws NoSuchMethodException {
+      Constructor<?> constructor =
+          writerClass.getDeclaredConstructor(JsonFieldInfo[].class, 
JsonCodec[].class);
+      constructor.setAccessible(true);
+      return constructor;
+    }
+
+    private static Constructor<?> readerConstructor(Class<?> readerClass)
+        throws NoSuchMethodException {
+      Constructor<?> constructor =
+          readerClass.getDeclaredConstructor(
+              JsonFieldInfo[].class, JsonCodec[].class, 
BaseObjectCodec[].class);
+      constructor.setAccessible(true);
+      return constructor;
+    }
+
+    private static MethodHandle writerHandle(Class<?> writerClass)
+        throws NoSuchMethodException, IllegalAccessException {
+      return _JDKAccess._trustedLookup(writerClass)
+          .findConstructor(
+              writerClass,
+              MethodType.methodType(void.class, JsonFieldInfo[].class, 
JsonCodec[].class));
+    }
+
+    private static MethodHandle readerHandle(Class<?> readerClass)
+        throws NoSuchMethodException, IllegalAccessException {
+      return _JDKAccess._trustedLookup(readerClass)
+          .findConstructor(
+              readerClass,
+              MethodType.methodType(
+                  void.class, JsonFieldInfo[].class, JsonCodec[].class, 
BaseObjectCodec[].class));
+    }
+  }
 }
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonJITContext.java 
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonJITContext.java
new file mode 100644
index 000000000..f651183ab
--- /dev/null
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonJITContext.java
@@ -0,0 +1,205 @@
+/*
+ * 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.json.codegen;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import org.apache.fory.annotation.Internal;
+import org.apache.fory.codegen.CodeGenerator;
+import org.apache.fory.json.resolver.JsonSharedRegistry;
+import org.apache.fory.util.ExceptionUtils;
+import org.apache.fory.util.Preconditions;
+
+/** Coordinates asynchronous JSON object-codec compilation and 
generated-result publication. */
+public final class JsonJITContext {
+  private final boolean asyncCompilationEnabled;
+  private final ReentrantLock jitLock;
+  private int jsonVisitState;
+  private final Map<Object, List<NotifyCallback>> hasJITResult;
+
+  public JsonJITContext(boolean asyncCompilationEnabled) {
+    this.asyncCompilationEnabled = asyncCompilationEnabled;
+    jitLock = new ReentrantLock(true);
+    hasJITResult = new HashMap<>();
+  }
+
+  public boolean asyncCompilationEnabled() {
+    return asyncCompilationEnabled;
+  }
+
+  @Internal
+  public <T> T registerObjectJITCallback(
+      Callable<T> interpreterModeAction, Callable<T> jitAction, 
ObjectJITCallback<T> callback) {
+    try {
+      lock();
+      Object id = callback.id();
+      if (asyncCompilationEnabled && !isAsyncVisitingJson()) {
+        List<NotifyCallback> callbacks = hasJITResult.get(id);
+        if (callbacks != null) {
+          callbacks.add(
+              new NotifyCallback() {
+                @Override
+                @SuppressWarnings("unchecked")
+                public void onNotifyResult(Object result) {
+                  callback.onSuccess((T) result);
+                }
+
+                @Override
+                public void onNotifyMissed() {}
+              });
+          return interpreterModeAction.call();
+        }
+        hasJITResult.put(id, new ArrayList<>());
+        ExecutorService compilationService = 
CodeGenerator.getCompilationService();
+        compilationService.execute(
+            () -> {
+              try {
+                T result = jitAction.call();
+                try {
+                  lock();
+                  // Keep this entry visible while the owner callback installs 
generated codecs.
+                  // Recursive codec construction can subscribe nested fields 
for the same type.
+                  List<NotifyCallback> notifyCallbacks = hasJITResult.get(id);
+                  callback.onSuccess(result);
+                  if (notifyCallbacks != null) {
+                    for (int i = 0; i < notifyCallbacks.size(); i++) {
+                      notifyCallbacks.get(i).onNotifyResult(result);
+                    }
+                  }
+                  hasJITResult.remove(id);
+                } finally {
+                  unlock();
+                }
+              } catch (Throwable t) {
+                try {
+                  lock();
+                  hasJITResult.remove(id);
+                  callback.onFailure(t);
+                } finally {
+                  unlock();
+                }
+              }
+            });
+        return interpreterModeAction.call();
+      }
+      return jitAction.call();
+    } catch (Exception e) {
+      ExceptionUtils.throwException(e);
+      throw new IllegalStateException("unreachable");
+    } finally {
+      unlock();
+    }
+  }
+
+  public void registerJITNotifyCallback(Object id, NotifyCallback 
notifyCallback) {
+    Preconditions.checkNotNull(id);
+    try {
+      lock();
+      List<NotifyCallback> notifyCallbacks = hasJITResult.get(id);
+      if (notifyCallbacks == null) {
+        notifyCallback.onNotifyMissed();
+      } else {
+        notifyCallbacks.add(notifyCallback);
+      }
+    } finally {
+      unlock();
+    }
+  }
+
+  @Internal
+  public <T> T asyncVisitJson(
+      JsonSharedRegistry sharedRegistry, Function<JsonSharedRegistry, T> 
function) {
+    try {
+      lock();
+      jsonVisitState++;
+      return function.apply(sharedRegistry);
+    } finally {
+      jsonVisitState--;
+      unlock();
+    }
+  }
+
+  private boolean isAsyncVisitingJson() {
+    if (asyncCompilationEnabled) {
+      try {
+        lock();
+        return jsonVisitState != 0;
+      } finally {
+        unlock();
+      }
+    }
+    return false;
+  }
+
+  public boolean hasJITResult(Object key) {
+    try {
+      lock();
+      return hasJITResult.get(key) != null;
+    } finally {
+      unlock();
+    }
+  }
+
+  @Internal
+  public void lock() {
+    if (asyncCompilationEnabled) {
+      jitLock.lock();
+    }
+  }
+
+  @Internal
+  public boolean lockedByCurrentThread() {
+    return !asyncCompilationEnabled || jitLock.isHeldByCurrentThread();
+  }
+
+  @Internal
+  public void unlock() {
+    if (asyncCompilationEnabled) {
+      jitLock.unlock();
+    }
+  }
+
+  @Internal
+  public interface ObjectJITCallback<T> {
+    void onSuccess(T result);
+
+    default void onFailure(Throwable e) {
+      e.printStackTrace();
+      ExceptionUtils.throwException(e);
+    }
+
+    Object id();
+  }
+
+  @Internal
+  public interface NotifyCallback {
+    default void onNotifyResult(Object result) {
+      onNotifyMissed();
+    }
+
+    void onNotifyMissed();
+  }
+}
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
index 282cbde9d..40556e962 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonSharedRegistry.java
@@ -78,10 +78,12 @@ import org.apache.fory.json.codec.CollectionCodec;
 import org.apache.fory.json.codec.GuavaCodecs;
 import org.apache.fory.json.codec.JsonCodec;
 import org.apache.fory.json.codec.MapCodec;
-import org.apache.fory.json.codec.ObjectCodecs;
+import org.apache.fory.json.codec.ObjectCodec;
 import org.apache.fory.json.codec.ScalarCodecs;
 import org.apache.fory.json.codec.SqlJsonCodecs;
 import org.apache.fory.json.codegen.JsonCodegen;
+import org.apache.fory.json.codegen.JsonCodegen.GeneratedObjectCodecClasses;
+import org.apache.fory.json.codegen.JsonJITContext;
 import org.apache.fory.json.meta.JsonFieldKind;
 import org.apache.fory.reflect.TypeRef;
 import org.apache.fory.type.BFloat16;
@@ -92,16 +94,17 @@ public final class JsonSharedRegistry {
   private final CodecRegistry customCodecs;
   private final IdentityHashMap<Class<?>, JsonCodec> exactCodecs;
   private final JsonCodegen codegen;
+  private final JsonJITContext jitContext;
   private final boolean propertyDiscoveryEnabled;
 
   public JsonSharedRegistry(JsonConfig config) {
     this.customCodecs = config.codecRegistry().copy();
     this.propertyDiscoveryEnabled = config.propertyDiscoveryEnabled();
     exactCodecs = new IdentityHashMap<>();
+    boolean codegenEnabled = config.codegenEnabled();
+    jitContext = new JsonJITContext(codegenEnabled && 
config.asyncCompilationEnabled());
     codegen =
-        config.codegenEnabled()
-            ? new JsonCodegen(config.writeNullFields(), config.getConfigHash())
-            : null;
+        codegenEnabled ? new JsonCodegen(config.writeNullFields(), 
config.getCodegenHash()) : null;
     registerExactCodecs();
   }
 
@@ -214,8 +217,47 @@ public final class JsonSharedRegistry {
     return JsonFieldKind.OBJECT;
   }
 
-  public ObjectCodecs compileObject(BaseObjectCodec codec, JsonTypeResolver 
localResolver) {
-    return codegen == null ? null : codegen.compile(codec, localResolver);
+  public BaseObjectCodec compileObject(
+      ObjectCodec codec,
+      JsonTypeResolver localResolver,
+      JsonJITContext.ObjectJITCallback<GeneratedObjectCodecClasses> callback) {
+    if (codegen == null || !codegen.canCompile(codec)) {
+      return null;
+    }
+    GeneratedObjectCodecClasses classes = 
codegen.generatedClasses(codec.type());
+    if (classes == null) {
+      classes =
+          jitContext.registerObjectJITCallback(
+              () -> null,
+              () -> jitContext.asyncVisitJson(this, ignored -> 
codegen.compileClasses(codec)),
+              callback);
+    }
+    return classes == null ? null : codegen.newCodec(codec, localResolver, 
classes);
+  }
+
+  BaseObjectCodec newGeneratedCodec(
+      ObjectCodec codec, JsonTypeResolver resolver, 
GeneratedObjectCodecClasses classes) {
+    return codegen.newCodec(codec, resolver, classes);
+  }
+
+  GeneratedObjectCodecClasses generatedClasses(Class<?> type) {
+    return codegen == null ? null : codegen.generatedClasses(type);
+  }
+
+  JsonJITContext jitContext() {
+    return jitContext;
+  }
+
+  boolean hasJITResult(Object id) {
+    return jitContext.hasJITResult(id);
+  }
+
+  boolean asyncCompilationEnabled() {
+    return jitContext.asyncCompilationEnabled();
+  }
+
+  void registerJITNotifyCallback(Object id, JsonJITContext.NotifyCallback 
callback) {
+    jitContext.registerJITNotifyCallback(id, callback);
   }
 
   boolean propertyDiscoveryEnabled() {
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java
index dd61f6922..05dc24f21 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeInfo.java
@@ -24,13 +24,13 @@ import org.apache.fory.json.codec.JsonCodec;
 import org.apache.fory.json.meta.JsonFieldKind;
 import org.apache.fory.reflect.TypeRef;
 
-/** Immutable JSON type binding resolved and owned by {@link 
JsonTypeResolver}. */
+/** JSON type binding resolved and owned by {@link JsonTypeResolver}. */
 public final class JsonTypeInfo {
   private final Type type;
   private final TypeRef<?> typeRef;
   private final Class<?> rawType;
   private final JsonFieldKind kind;
-  private final JsonCodec codec;
+  private JsonCodec codec;
   private final boolean primitive;
 
   JsonTypeInfo(
@@ -63,6 +63,10 @@ public final class JsonTypeInfo {
     return codec;
   }
 
+  void setCodec(JsonCodec codec) {
+    this.codec = codec;
+  }
+
   public boolean primitive() {
     return primitive;
   }
diff --git 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
index 8b0e43e74..3b363fffc 100644
--- 
a/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
+++ 
b/java/fory-json/src/main/java/org/apache/fory/json/resolver/JsonTypeResolver.java
@@ -20,14 +20,19 @@
 package org.apache.fory.json.resolver;
 
 import java.lang.reflect.Type;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 import org.apache.fory.json.codec.BaseObjectCodec;
 import org.apache.fory.json.codec.CodecUtils;
 import org.apache.fory.json.codec.JsonCodec;
 import org.apache.fory.json.codec.ObjectCodec;
-import org.apache.fory.json.codec.ObjectCodecs;
+import org.apache.fory.json.codegen.JsonCodegen.GeneratedObjectCodecClasses;
+import org.apache.fory.json.codegen.JsonJITContext;
 import org.apache.fory.reflect.TypeRef;
 
 /**
@@ -37,9 +42,10 @@ import org.apache.fory.reflect.TypeRef;
  * Runtime JSON values, including non-enumerated string or number 
values/tokens, must stay uncached.
  */
 public final class JsonTypeResolver {
-  private final IdentityHashMap<Class<?>, BaseObjectCodec> objectCodecs = new 
IdentityHashMap<>();
-  private final Map<Object, JsonTypeInfo> typeInfos = new HashMap<>();
+  private final Map<Class<?>, BaseObjectCodec> objectCodecs;
+  private final Map<Object, JsonTypeInfo> typeInfos;
   private final JsonSharedRegistry sharedRegistry;
+  private final Set<Class<?>> installingCodecs;
 
   private enum RuntimeObjectKey {
     INSTANCE
@@ -47,14 +53,31 @@ public final class JsonTypeResolver {
 
   public JsonTypeResolver(JsonSharedRegistry sharedRegistry) {
     this.sharedRegistry = sharedRegistry;
+    if (sharedRegistry.asyncCompilationEnabled()) {
+      objectCodecs = new ConcurrentHashMap<>();
+      typeInfos = new ConcurrentHashMap<>();
+    } else {
+      objectCodecs = new IdentityHashMap<>();
+      typeInfos = new HashMap<>();
+    }
+    installingCodecs = Collections.newSetFromMap(new 
ConcurrentHashMap<Class<?>, Boolean>());
   }
 
   public BaseObjectCodec getObjectCodec(Class<?> type) {
     BaseObjectCodec codec = objectCodecs.get(type);
     if (codec != null) {
-      return codec;
+      return generatedCodecIfReady(type, codec);
+    }
+    try {
+      sharedRegistry.jitContext().lock();
+      codec = objectCodecs.get(type);
+      if (codec != null) {
+        return generatedCodecIfReady(type, codec);
+      }
+      return buildObjectCodec(type);
+    } finally {
+      sharedRegistry.jitContext().unlock();
     }
-    return buildObjectCodec(type);
   }
 
   public JsonTypeInfo getTypeInfo(Type declaredType, Class<?> fallback) {
@@ -64,7 +87,16 @@ public final class JsonTypeResolver {
     if (typeInfo != null) {
       return typeInfo;
     }
-    return buildTypeInfo(key, rawType, declaredType);
+    try {
+      sharedRegistry.jitContext().lock();
+      typeInfo = typeInfos.get(key);
+      if (typeInfo != null) {
+        return typeInfo;
+      }
+      return buildTypeInfo(key, rawType, declaredType);
+    } finally {
+      sharedRegistry.jitContext().unlock();
+    }
   }
 
   public JsonTypeInfo getRuntimeTypeInfo(Class<?> runtimeType) {
@@ -73,7 +105,16 @@ public final class JsonTypeResolver {
     if (typeInfo != null) {
       return typeInfo;
     }
-    return buildRuntimeTypeInfo(key, runtimeType);
+    try {
+      sharedRegistry.jitContext().lock();
+      typeInfo = typeInfos.get(key);
+      if (typeInfo != null) {
+        return typeInfo;
+      }
+      return buildRuntimeTypeInfo(key, runtimeType);
+    } finally {
+      sharedRegistry.jitContext().unlock();
+    }
   }
 
   private BaseObjectCodec buildObjectCodec(Class<?> type) {
@@ -87,11 +128,27 @@ public final class JsonTypeResolver {
     objectCodecs.put(type, codec);
     try {
       codec.resolveTypes(this);
-      ObjectCodecs codecs = sharedRegistry.compileObject(codec, this);
-      if (codecs != null) {
-        BaseObjectCodec generated = codec.withCodecs(codecs);
-        objectCodecs.put(type, generated);
-        return generated;
+      BaseObjectCodec compiled =
+          sharedRegistry.compileObject(
+              codec,
+              this,
+              new 
JsonJITContext.ObjectJITCallback<GeneratedObjectCodecClasses>() {
+                @Override
+                public void onSuccess(GeneratedObjectCodecClasses result) {
+                  BaseObjectCodec generated = newGeneratedCodec(codec, result);
+                  if (generated != null) {
+                    setObjectCodec(type, generated);
+                  }
+                }
+
+                @Override
+                public Object id() {
+                  return type;
+                }
+              });
+      if (compiled != null && compiled != codec) {
+        setObjectCodec(type, compiled);
+        return compiled;
       }
       return codec;
     } catch (RuntimeException | Error e) {
@@ -116,7 +173,10 @@ public final class JsonTypeResolver {
     if (codec == null) {
       codec = getObjectCodec(rawType);
     }
-    return new JsonTypeInfo(declaredType, typeRef, rawType, 
sharedRegistry.kind(rawType), codec);
+    JsonTypeInfo typeInfo =
+        new JsonTypeInfo(declaredType, typeRef, rawType, 
sharedRegistry.kind(rawType), codec);
+    registerCodecUpdate(typeInfo, codec);
+    return typeInfo;
   }
 
   private JsonTypeInfo buildRuntimeTypeInfo(Object key, Class<?> rawType) {
@@ -134,10 +194,123 @@ public final class JsonTypeResolver {
     }
     JsonTypeInfo typeInfo =
         new JsonTypeInfo(rawType, typeRef, rawType, 
sharedRegistry.kind(rawType), codec);
+    registerCodecUpdate(typeInfo, codec);
     typeInfos.put(key, typeInfo);
     return typeInfo;
   }
 
+  public void registerJITNotifyCallback(JsonCodec currentCodec, 
Consumer<JsonCodec> updater) {
+    if (!(currentCodec instanceof BaseObjectCodec)) {
+      return;
+    }
+    BaseObjectCodec objectCodec = (BaseObjectCodec) currentCodec;
+    Class<?> type = objectCodec.type();
+    if (!sharedRegistry.hasJITResult(type)) {
+      BaseObjectCodec latest = generatedCodecIfReady(type, objectCodec);
+      if (latest != objectCodec) {
+        updater.accept(latest);
+      }
+      return;
+    }
+    sharedRegistry.registerJITNotifyCallback(
+        type,
+        new JsonJITContext.NotifyCallback() {
+          @Override
+          public void onNotifyResult(Object result) {
+            BaseObjectCodec latest = generatedCodecIfReady(type, objectCodec);
+            if (latest != objectCodec) {
+              updater.accept(latest);
+              return;
+            }
+            if (result instanceof GeneratedObjectCodecClasses
+                && objectCodec instanceof ObjectCodec) {
+              BaseObjectCodec generated =
+                  newGeneratedCodec(
+                      (ObjectCodec) objectCodec, (GeneratedObjectCodecClasses) 
result);
+              if (generated != null) {
+                setObjectCodec(type, generated);
+                updater.accept(generated);
+                return;
+              }
+            }
+            onNotifyMissed();
+          }
+
+          @Override
+          public void onNotifyMissed() {
+            BaseObjectCodec latest = getObjectCodec(type);
+            if (latest != objectCodec) {
+              updater.accept(latest);
+            }
+          }
+        });
+  }
+
+  private void registerCodecUpdate(JsonTypeInfo typeInfo, JsonCodec codec) {
+    registerJITNotifyCallback(codec, typeInfo::setCodec);
+  }
+
+  private void setObjectCodec(Class<?> type, BaseObjectCodec codec) {
+    objectCodecs.put(type, codec);
+    if (type == Object.class) {
+      JsonTypeInfo runtimeTypeInfo = typeInfos.get(RuntimeObjectKey.INSTANCE);
+      if (runtimeTypeInfo != null) {
+        runtimeTypeInfo.setCodec(codec);
+      }
+      return;
+    }
+    JsonTypeInfo typeInfo = typeInfos.get(type);
+    if (typeInfo != null) {
+      typeInfo.setCodec(codec);
+    }
+  }
+
+  private BaseObjectCodec newGeneratedCodec(
+      ObjectCodec codec, GeneratedObjectCodecClasses classes) {
+    Class<?> type = codec.type();
+    // Generated codec construction may request a recursive object codec. Keep 
the current
+    // interpreter until the outer generated codec is installed and its 
pending callbacks run.
+    if (!installingCodecs.add(type)) {
+      return codec;
+    }
+    try {
+      return sharedRegistry.newGeneratedCodec(codec, this, classes);
+    } finally {
+      installingCodecs.remove(type);
+    }
+  }
+
+  private BaseObjectCodec generatedCodecIfReady(Class<?> type, BaseObjectCodec 
codec) {
+    if (!(codec instanceof ObjectCodec)) {
+      return codec;
+    }
+    if (installingCodecs.contains(type)) {
+      return codec;
+    }
+    GeneratedObjectCodecClasses classes = 
sharedRegistry.generatedClasses(type);
+    if (classes == null) {
+      return codec;
+    }
+    if (!sharedRegistry.jitContext().lockedByCurrentThread()) {
+      try {
+        sharedRegistry.jitContext().lock();
+        return generatedCodecIfReady(type, codec);
+      } finally {
+        sharedRegistry.jitContext().unlock();
+      }
+    }
+    BaseObjectCodec latest = objectCodecs.get(type);
+    if (latest != null && latest != codec) {
+      return latest;
+    }
+    BaseObjectCodec generated = newGeneratedCodec((ObjectCodec) codec, 
classes);
+    if (generated != null) {
+      setObjectCodec(type, generated);
+      return generated;
+    }
+    return codec;
+  }
+
   private static Object typeInfoKey(Type declaredType, Class<?> rawType) {
     if (declaredType instanceof Class) {
       return rawType;
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java 
b/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java
index 390eca491..8f4304336 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/ForyJsonTestModels.java
@@ -52,6 +52,7 @@ import org.apache.fory.json.data.TokenValues;
 import org.apache.fory.json.data.UnicodeMatrix;
 import org.apache.fory.reflect.FieldAccessor;
 import org.testng.SkipException;
+import org.testng.annotations.DataProvider;
 
 public abstract class ForyJsonTestModels {
   protected static final String TWO_BYTE_TEXT = JsonTestData.TWO_BYTE_TEXT;
@@ -61,6 +62,28 @@ public abstract class ForyJsonTestModels {
   protected static final String COMBINING_TEXT = JsonTestData.COMBINING_TEXT;
   protected static final String ZH_TEXT = JsonTestData.ZH_TEXT;
   protected static final String EU_TEXT = JsonTestData.EU_TEXT;
+  private final boolean codegen;
+
+  protected ForyJsonTestModels(boolean codegen) {
+    this.codegen = codegen;
+  }
+
+  @DataProvider(name = "codegen")
+  public static Object[][] codegen() {
+    return new Object[][] {{false}, {true}};
+  }
+
+  protected final ForyJsonBuilder newJsonBuilder() {
+    return ForyJson.builder().withCodegen(codegen).withAsyncCompilation(false);
+  }
+
+  protected final ForyJson newJson() {
+    return newJsonBuilder().build();
+  }
+
+  protected final boolean codegenEnabled() {
+    return codegen;
+  }
 
   protected static TokenValues tokenValue(int count, String name, List<String> 
tags, long total) {
     TokenValues value = new TokenValues();
@@ -268,8 +291,8 @@ public abstract class ForyJsonTestModels {
         json.fromJson(objectJson.getBytes(StandardCharsets.UTF_8), 
PublicFields.class).name, text);
   }
 
-  protected static void assertGeneratedWhenSupported(ForyJson json, Class<?> 
type) {
-    assertTrue(json.hasGeneratedWriter(type));
+  protected final void assertGeneratedWhenSupported(ForyJson json, Class<?> 
type) {
+    assertEquals(json.hasGeneratedWriter(type), codegen);
   }
 
   protected static String repeat(char ch, int length) {
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
new file mode 100644
index 000000000..0221ab1de
--- /dev/null
+++ 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonAsyncCompilationTest.java
@@ -0,0 +1,360 @@
+/*
+ * 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.json;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.fory.json.codec.BaseObjectCodec;
+import org.apache.fory.json.codec.GeneratedObjectCodec;
+import org.apache.fory.json.codec.JsonCodec;
+import org.apache.fory.json.data.RecursiveParent;
+import org.apache.fory.json.resolver.JsonSharedRegistry;
+import org.apache.fory.json.resolver.JsonTypeResolver;
+import org.testng.annotations.Test;
+
+public class JsonAsyncCompilationTest {
+  @Test
+  public void defaultBuilderEnablesAsync() throws Exception {
+    assertTrue(asyncCompilationEnabled(ForyJson.builder().build()));
+    
assertFalse(asyncCompilationEnabled(ForyJson.builder().withAsyncCompilation(false).build()));
+    
assertFalse(asyncCompilationEnabled(ForyJson.builder().withCodegen(false).build()));
+  }
+
+  @Test
+  public void asyncCompilationPublishesGeneratedCodec() throws Exception {
+    ForyJson json = ForyJson.builder().build();
+    AsyncChild value = child("root", 1);
+    assertEquals(json.toJson(value), "{\"id\":1,\"name\":\"root\"}");
+    awaitGenerated(json, AsyncChild.class);
+    assertEquals(json.toJson(value), "{\"id\":1,\"name\":\"root\"}");
+    assertEquals(json.fromJson("{\"id\":2,\"name\":\"read\"}", 
AsyncChild.class).name, "read");
+  }
+
+  @Test
+  public void asyncUpdatesCachedNestedCodecs() throws Exception {
+    ForyJson json = ForyJson.builder().build();
+    AsyncParent value = parent();
+    String expected =
+        
"{\"array\":[{\"id\":2,\"name\":\"array\"}],\"atomic\":{\"id\":6,\"name\":\"atomic\"},"
+            + 
"\"child\":{\"id\":1,\"name\":\"child\"},\"list\":[{\"id\":3,\"name\":\"list\"}],"
+            + "\"map\":{\"entry\":{\"id\":4,\"name\":\"map\"}},"
+            + "\"optional\":{\"id\":5,\"name\":\"optional\"}}";
+    assertEquals(json.toJson(value), expected);
+    awaitGenerated(json, AsyncParent.class);
+    awaitGenerated(json, AsyncChild.class);
+    assertEquals(json.toJson(value), expected);
+    AsyncParent read = json.fromJson(expected, AsyncParent.class);
+    assertEquals(read.child.name, "child");
+    assertEquals(read.array[0].name, "array");
+    assertEquals(read.list.get(0).name, "list");
+    assertEquals(read.map.get("entry").name, "map");
+    assertEquals(read.optional.get().name, "optional");
+    assertEquals(read.atomic.get().name, "atomic");
+    assertCachedGeneratedChild(json, AsyncParent.class, AsyncChild.class);
+  }
+
+  @Test
+  public void objectDeclaredCodecsKeepNaturalSemantics() throws Exception {
+    ForyJson json = ForyJson.builder().build();
+    ObjectHolder holder = new ObjectHolder();
+    holder.array = new Object[] {"array", 7, child("array-child", 8)};
+    holder.atomic = new AtomicReference<Object>("atomic");
+    holder.map = new LinkedHashMap<>();
+    holder.map.put("string", "value");
+    holder.map.put("number", 9);
+    holder.optional = Optional.of("optional");
+    String expected =
+        "{\"array\":[\"array\",7,{\"id\":8,\"name\":\"array-child\"}],"
+            + 
"\"atomic\":\"atomic\",\"map\":{\"string\":\"value\",\"number\":9},"
+            + "\"optional\":\"optional\"}";
+    withJitLocked(
+        json,
+        () -> {
+          json.hasGeneratedWriter(Object.class);
+          assertEquals(json.toJson(holder), expected);
+        });
+    awaitGenerated(json, Object.class);
+    assertEquals(json.toJson(holder), expected);
+  }
+
+  @Test
+  public void objectClassJitKeepsNaturalObjectBinding() throws Exception {
+    ForyJson json = ForyJson.builder().build();
+    assertTrue(json.fromJson("[1]", Object.class) instanceof JSONArray);
+    JSONObject before =
+        (JSONObject) json.fromJson("{\"items\":[1],\"name\":\"fory\"}", 
Object.class);
+    assertTrue(before.get("items") instanceof JSONArray);
+    json.hasGeneratedWriter(Object.class);
+    awaitGenerated(json, Object.class);
+    assertEquals(json.fromJson("7", Object.class), Long.valueOf(7));
+    assertTrue(json.fromJson("[1]", Object.class) instanceof JSONArray);
+    JSONObject after =
+        (JSONObject) json.fromJson("{\"items\":[1],\"name\":\"fory\"}", 
Object.class);
+    assertTrue(after.get("items") instanceof JSONArray);
+    assertEquals(after.get("name"), "fory");
+  }
+
+  @Test
+  public void asyncCompilationKeepsResolverLocalCodecs() throws Exception {
+    ForyJson json = ForyJson.builder().build();
+    JsonSharedRegistry sharedRegistry = (JsonSharedRegistry) field(json, 
"sharedRegistry");
+    JsonTypeResolver first = new JsonTypeResolver(sharedRegistry);
+    JsonTypeResolver second = new JsonTypeResolver(sharedRegistry);
+    withJitLocked(
+        json,
+        () -> {
+          assertFalse(first.getObjectCodec(AsyncChild.class) instanceof 
GeneratedObjectCodec);
+          assertFalse(second.getObjectCodec(AsyncChild.class) instanceof 
GeneratedObjectCodec);
+          assertEquals(pendingJitCount(json), 1);
+        });
+    BaseObjectCodec firstCodec = awaitGenerated(first, AsyncChild.class);
+    BaseObjectCodec secondCodec = awaitGenerated(second, AsyncChild.class);
+    assertTrue(firstCodec instanceof GeneratedObjectCodec, 
firstCodec.getClass().getName());
+    assertTrue(secondCodec instanceof GeneratedObjectCodec, 
secondCodec.getClass().getName());
+    assertTrue(firstCodec != secondCodec);
+    assertEquals(generatedUtf8WriterClass(firstCodec), 
generatedUtf8WriterClass(secondCodec));
+  }
+
+  @Test
+  public void asyncReusesGeneratedClassesForNewResolver() throws Exception {
+    ForyJson json = ForyJson.builder().build();
+    JsonSharedRegistry sharedRegistry = (JsonSharedRegistry) field(json, 
"sharedRegistry");
+    JsonTypeResolver first = new JsonTypeResolver(sharedRegistry);
+    assertFalse(first.getObjectCodec(AsyncChild.class) instanceof 
GeneratedObjectCodec);
+    BaseObjectCodec firstCodec = awaitGenerated(first, AsyncChild.class);
+    JsonTypeResolver second = new JsonTypeResolver(sharedRegistry);
+    BaseObjectCodec secondCodec = second.getObjectCodec(AsyncChild.class);
+    assertTrue(secondCodec instanceof GeneratedObjectCodec, 
secondCodec.getClass().getName());
+    assertTrue(firstCodec != secondCodec);
+    assertEquals(generatedUtf8WriterClass(firstCodec), 
generatedUtf8WriterClass(secondCodec));
+  }
+
+  @Test
+  public void asyncRecursiveTypes() throws Exception {
+    ForyJson json = ForyJson.builder().build();
+    RecursiveParent value = new RecursiveParent();
+    assertEquals(json.toJson(value), 
"{\"child\":{\"name\":\"child\"},\"name\":\"parent\"}");
+    awaitGenerated(json, RecursiveParent.class);
+    assertEquals(json.toJson(value), 
"{\"child\":{\"name\":\"child\"},\"name\":\"parent\"}");
+  }
+
+  @Test
+  public void disabledCodegenIgnoresAsync() throws Exception {
+    ForyJson json = ForyJson.builder().withCodegen(false).build();
+    assertEquals(json.toJson(child("root", 1)), 
"{\"id\":1,\"name\":\"root\"}");
+    Thread.sleep(50);
+    assertFalse(json.hasGeneratedWriter(AsyncChild.class));
+  }
+
+  private static AsyncParent parent() {
+    AsyncParent parent = new AsyncParent();
+    parent.child = child("child", 1);
+    parent.array = new AsyncChild[] {child("array", 2)};
+    parent.list = Collections.singletonList(child("list", 3));
+    parent.map = new LinkedHashMap<>();
+    parent.map.put("entry", child("map", 4));
+    parent.optional = Optional.of(child("optional", 5));
+    parent.atomic = new AtomicReference<>(child("atomic", 6));
+    return parent;
+  }
+
+  private static AsyncChild child(String name, int id) {
+    AsyncChild child = new AsyncChild();
+    child.name = name;
+    child.id = id;
+    return child;
+  }
+
+  private static void awaitGenerated(ForyJson json, Class<?> type) throws 
InterruptedException {
+    for (int i = 0; i < 200; i++) {
+      if (json.hasGeneratedWriter(type)) {
+        return;
+      }
+      Thread.sleep(10);
+    }
+    fail("Timed out waiting for generated JSON codec for " + type);
+  }
+
+  private static BaseObjectCodec awaitGenerated(JsonTypeResolver resolver, 
Class<?> type)
+      throws InterruptedException {
+    for (int i = 0; i < 200; i++) {
+      BaseObjectCodec codec = resolver.getObjectCodec(type);
+      if (codec instanceof GeneratedObjectCodec) {
+        return codec;
+      }
+      Thread.sleep(10);
+    }
+    fail("Timed out waiting for generated JSON codec for " + type);
+    throw new IllegalStateException("unreachable");
+  }
+
+  private static boolean asyncCompilationEnabled(ForyJson json) throws 
Exception {
+    Object sharedRegistry = field(json, "sharedRegistry");
+    Object jitContext = field(sharedRegistry, "jitContext");
+    return (boolean) field(jitContext, "asyncCompilationEnabled");
+  }
+
+  private static int pendingJitCount(ForyJson json) throws Exception {
+    Object sharedRegistry = field(json, "sharedRegistry");
+    Object jitContext = field(sharedRegistry, "jitContext");
+    return ((Map<?, ?>) field(jitContext, "hasJITResult")).size();
+  }
+
+  private static Class<?> generatedUtf8WriterClass(BaseObjectCodec codec) 
throws Exception {
+    Field field = GeneratedObjectCodec.class.getDeclaredField("utf8Writer");
+    field.setAccessible(true);
+    return field.get(codec).getClass();
+  }
+
+  private static void assertCachedGeneratedChild(
+      ForyJson json, Class<?> parentType, Class<?> childType) throws Exception 
{
+    GeneratedObjectCodec codec = generatedObjectCodec(json, parentType);
+    AtomicInteger cachedChildFields = new AtomicInteger();
+    scanCachedCodecs(
+        codec, childType, cachedChildFields, Collections.newSetFromMap(new 
IdentityHashMap<>()));
+    assertTrue(
+        cachedChildFields.get() >= 6,
+        "Expected cached generated child codecs, got " + 
cachedChildFields.get());
+  }
+
+  private static GeneratedObjectCodec generatedObjectCodec(ForyJson json, 
Class<?> type)
+      throws Exception {
+    Object primarySlot = ((AtomicReference<?>) field(json, 
"primarySlot")).get();
+    Object state = field(primarySlot, "state");
+    Object resolver = field(state, "typeResolver");
+    BaseObjectCodec codec =
+        (BaseObjectCodec)
+            resolver.getClass().getMethod("getObjectCodec", 
Class.class).invoke(resolver, type);
+    assertTrue(codec instanceof GeneratedObjectCodec);
+    return (GeneratedObjectCodec) codec;
+  }
+
+  private static void scanCachedCodecs(
+      Object owner, Class<?> childType, AtomicInteger cachedChildFields, 
java.util.Set<Object> seen)
+      throws Exception {
+    if (owner == null || !seen.add(owner)) {
+      return;
+    }
+    if (owner instanceof BaseObjectCodec) {
+      BaseObjectCodec codec = (BaseObjectCodec) owner;
+      if (codec.type() == childType) {
+        assertTrue(codec instanceof GeneratedObjectCodec, 
codec.getClass().getName());
+        cachedChildFields.incrementAndGet();
+      }
+      if (!(codec instanceof GeneratedObjectCodec)) {
+        return;
+      }
+    }
+    Class<?> type = owner.getClass();
+    if (!isCodecOwner(type)) {
+      return;
+    }
+    for (Field field : allFields(type)) {
+      if (Modifier.isStatic(field.getModifiers()) || 
field.getType().isPrimitive()) {
+        continue;
+      }
+      field.setAccessible(true);
+      Object value = field.get(owner);
+      if (value instanceof BaseObjectCodec && ((BaseObjectCodec) value).type() 
== childType) {
+        assertTrue(value instanceof GeneratedObjectCodec, 
value.getClass().getName());
+        cachedChildFields.incrementAndGet();
+        continue;
+      }
+      if (value instanceof BaseObjectCodec
+          || value instanceof JsonCodec
+          || isCodecOwner(value == null ? null : value.getClass())) {
+        scanCachedCodecs(value, childType, cachedChildFields, seen);
+      }
+    }
+  }
+
+  private static boolean isCodecOwner(Class<?> type) {
+    if (type == null) {
+      return false;
+    }
+    Package pkg = type.getPackage();
+    String packageName = pkg == null ? "" : pkg.getName();
+    return packageName.startsWith("org.apache.fory.json.codec")
+        || type.getSimpleName().contains("ForyJsonCodec");
+  }
+
+  private static java.util.List<Field> allFields(Class<?> type) {
+    java.util.ArrayList<Field> fields = new java.util.ArrayList<>();
+    for (Class<?> current = type; current != null; current = 
current.getSuperclass()) {
+      fields.addAll(Arrays.asList(current.getDeclaredFields()));
+    }
+    return fields;
+  }
+
+  private static Object field(Object owner, String name) throws Exception {
+    Field field = owner.getClass().getDeclaredField(name);
+    field.setAccessible(true);
+    return field.get(owner);
+  }
+
+  private static void withJitLocked(ForyJson json, ThrowingRunnable action) 
throws Exception {
+    Object sharedRegistry = field(json, "sharedRegistry");
+    Object jitContext = field(sharedRegistry, "jitContext");
+    jitContext.getClass().getMethod("lock").invoke(jitContext);
+    try {
+      action.run();
+    } finally {
+      jitContext.getClass().getMethod("unlock").invoke(jitContext);
+    }
+  }
+
+  private interface ThrowingRunnable {
+    void run() throws Exception;
+  }
+
+  public static final class AsyncParent {
+    public AsyncChild[] array;
+    public AtomicReference<AsyncChild> atomic;
+    public AsyncChild child;
+    public java.util.List<AsyncChild> list;
+    public Map<String, AsyncChild> map;
+    public Optional<AsyncChild> optional;
+  }
+
+  public static final class AsyncChild {
+    public int id;
+    public String name;
+  }
+
+  public static final class ObjectHolder {
+    public Object[] array;
+    public AtomicReference<Object> atomic;
+    public Map<String, Object> map;
+    public Optional<Object> optional;
+  }
+}
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
index f6dae3ec1..bb1e18606 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonContainerTest.java
@@ -65,12 +65,18 @@ import org.apache.fory.json.data.MapKeyFields;
 import org.apache.fory.json.data.Nested;
 import org.apache.fory.json.data.TokenValues;
 import org.apache.fory.reflect.TypeRef;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonContainerTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonContainerTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void writeNestedCollections() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(
         json.toJson(new Nested()),
         
"{\"kind\":\"FAST\",\"names\":[\"a\",\"b\"],\"scores\":{\"one\":1,\"two\":2}}");
@@ -78,7 +84,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readTypeRefList() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     List<TokenValues> values =
         json.fromJson(
             
"[{\"count\":1,\"name\":\"alpha\",\"tags\":[\"x\",\"y\"],\"total\":2},"
@@ -93,7 +99,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readCollectionSubclassElementType() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Shelf shelf = json.fromJson("{\"notes\":[{\"title\":\"first\"}]}", 
Shelf.class);
     assertEquals(shelf.notes.get(0).getClass(), Note.class);
     assertEquals(shelf.notes.get(0).title, "first");
@@ -101,7 +107,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readMapSubclassValueType() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     PaletteGroups groups = json.fromJson("{\"warm\":{\"primary\":\"red\"}}", 
PaletteGroups.class);
     assertEquals(groups.get("warm").getClass(), PaletteCodes.class);
     assertEquals(groups.get("warm").get("primary"), "red");
@@ -109,7 +115,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readTypeRefMapBytes() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     byte[] bytes =
         
"{\"first\":{\"count\":5,\"name\":\"gamma\",\"tags\":[\"u\"],\"total\":6}}"
             .getBytes(StandardCharsets.UTF_8);
@@ -123,7 +129,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readJsonContainers() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     JSONObject object =
         json.fromJson("{\"name\":\"fory\",\"items\":[1,\"你好,Fory\"]}", 
JSONObject.class);
     assertEquals(object.get("name"), "fory");
@@ -139,7 +145,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void parsedContainersStartSmall() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     JSONArray array = json.fromJson("[1]", JSONArray.class);
     assertEquals(arrayCapacity(array), 1);
     assertEquals(arrayCapacity(json.fromJson("[]", JSONArray.class)), 0);
@@ -180,7 +186,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void writeJsonContainers() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     JSONObject object = new JSONObject();
     JSONArray values = new JSONArray();
     values.add(Integer.valueOf(1));
@@ -194,7 +200,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadMapKeyFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected =
         
"{\"intNames\":{\"1\":\"one\",\"2\":\"two\"},\"scores\":{\"FAST\":1,\"SMALL\":2}}";
     assertEquals(json.toJson(new MapKeyFields()), expected);
@@ -205,7 +211,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void writeRootMapNumericKeys() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Map<Integer, Integer> value =
         json.fromJson("{\"7\":70,\"8\":80}", new TypeRef<Map<Integer, 
Integer>>() {});
     assertEquals(json.toJson(new LinkedHashMap<>(value)), 
"{\"7\":70,\"8\":80}");
@@ -213,7 +219,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readTypeRefOptional() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Optional<TokenValues> value =
         json.fromJson(
             
"{\"count\":9,\"name\":\"optional\",\"tags\":[\"a\"],\"total\":10}",
@@ -226,7 +232,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readTypeRefMapKeys() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Map<Integer, String> value =
         json.fromJson("{\"1\":\"one\",\"2\":\"two\"}", new 
TypeRef<Map<Integer, String>>() {});
     assertEquals(value, intNames());
@@ -253,7 +259,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readFastContainerTypeRefs() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(
         json.fromJson("[\"alpha\",\"你好,Fory\"]", new TypeRef<List<String>>() 
{}),
         Arrays.asList("alpha", ZH_TEXT));
@@ -285,7 +291,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readDeclaredJdkContainers() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     DeclaredJdkContainers value =
         json.fromJson(
             
"{\"collection\":[\"a\",\"b\"],\"list\":[\"c\"],\"sequential\":[\"d\"],"
@@ -312,7 +318,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readMutableContainersUnchanged() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     List<String> list = json.fromJson("[\"a\"]", new TypeRef<List<String>>() 
{});
     assertTrue(list instanceof ArrayList);
     assertEquals(list, Collections.singletonList("a"));
@@ -338,7 +344,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readEnumContainersStillWork() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     EnumContainers value =
         json.fromJson(
             "{\"kinds\":[\"FAST\",\"SMALL\"],\"scores\":{\"FAST\":1}}", 
EnumContainers.class);
@@ -350,7 +356,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void rejectRuntimeContainerClasses() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertUnsupportedCollectionClass(json, Arrays.asList("a"));
     assertUnsupportedCollectionClass(json, Collections.emptyList());
     assertUnsupportedCollectionClass(json, Collections.singletonList("a"));
@@ -371,7 +377,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readGuavaImmutableContainers() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     GuavaContainers value =
         json.fromJson(
             "{\"list\":[\"a\",\"b\"],\"set\":[\"c\",\"d\"],"
@@ -394,7 +400,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void rejectGuavaImmutableNulls() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(
         ForyJsonException.class,
         () -> json.fromJson("[\"a\",null]", new 
TypeRef<ImmutableList<String>>() {}));
@@ -406,7 +412,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadFastContainerFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String input =
         
"{\"booleans\":[true,false],\"flags\":{\"enabled\":true,\"disabled\":false},"
             + "\"intNames\":{\"1\":\"one\",\"2\":\"two\"},\"ints\":[1,2],"
@@ -419,7 +425,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readPrimitiveArrayRoots() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new int[] {1, 2}), "[1,2]");
     assertEquals(json.fromJson("[1,2]", int[].class), new int[] {1, 2});
     assertEquals(
@@ -434,7 +440,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readUtf8StringArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(
         json.fromJson("[\"a\"]".getBytes(StandardCharsets.UTF_8), 
String[].class),
         new String[] {"a"});
@@ -453,7 +459,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readStringInputStringArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.fromJson("[\"a\"]", String[].class), new String[] {"a"});
     assertEquals(
         json.fromJson("[\"a\",null,\"b\",\"c\",\"d\",\"e\",\"f\",\"g\"]", 
String[].class),
@@ -469,7 +475,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readUtf8LongArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(
         json.fromJson("[7]".getBytes(StandardCharsets.UTF_8), long[].class), 
new long[] {7L});
     assertEquals(
@@ -485,7 +491,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readStringInputLongArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.fromJson("[7]", long[].class), new long[] {7L});
     assertEquals(
         json.fromJson("[1,2,3,4,5,6,7,8]", long[].class),
@@ -503,7 +509,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadBoxedPrimitiveArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new Integer[] {1, null, -2}), "[1,null,-2]");
     assertEquals(
         json.fromJson("[1,null,-2]".getBytes(StandardCharsets.UTF_8), 
Integer[].class),
@@ -537,7 +543,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readReferenceObjectArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Note[] notes = 
json.fromJson("[{\"title\":\"one\"},null,{\"title\":\"two\"}]", Note[].class);
     assertEquals(notes.getClass(), Note[].class);
     assertEquals(notes.length, 3);
@@ -566,7 +572,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void readReentrantObjectArray() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     ArrayNode[] roots =
         json.fromJson(
             
"[{\"name\":\"root\",\"children\":[{\"name\":\"leaf\",\"children\":[]}]}]"
@@ -600,7 +606,7 @@ public class JsonContainerTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadAtomicArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new AtomicIntegerArray(new int[] {1, -2, 3})), 
"[1,-2,3]");
     assertAtomicInts(json.fromJson("[1,-2,3]", AtomicIntegerArray.class), 1, 
-2, 3);
     assertEquals(
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java
index 701c40d2f..8c5f4a7f9 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonDepthTest.java
@@ -27,12 +27,18 @@ import java.nio.charset.StandardCharsets;
 import java.util.Map;
 import org.apache.fory.json.data.DepthNode;
 import org.apache.fory.reflect.TypeRef;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonDepthTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonDepthTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void defaultMaxDepth() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertTrue(
         json.fromJson(nestedArray(ForyJson.DEFAULT_MAX_DEPTH), Object.class) 
instanceof JSONArray);
     assertThrows(
@@ -42,7 +48,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
 
   @Test
   public void readMaxDepth() {
-    ForyJson json = ForyJson.builder().maxDepth(2).build();
+    ForyJson json = newJsonBuilder().maxDepth(2).build();
     assertEquals(json.fromJson("{\"child\":{\"value\":1}}", 
DepthNode.class).child.value, 1);
     assertThrows(
         ForyJsonException.class,
@@ -57,7 +63,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
 
   @Test
   public void readContainerMaxDepth() {
-    ForyJson json = ForyJson.builder().maxDepth(2).build();
+    ForyJson json = newJsonBuilder().maxDepth(2).build();
     assertTrue(json.fromJson("[[1]]", Object.class) instanceof JSONArray);
     assertThrows(ForyJsonException.class, () -> json.fromJson("[[[1]]]", 
Object.class));
     assertEquals(
@@ -66,7 +72,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
         ForyJsonException.class,
         () -> json.fromJson("{\"a\":{\"b\":{\"c\":1}}}", new 
TypeRef<Map<String, Object>>() {}));
 
-    ForyJson nestedJson = ForyJson.builder().maxDepth(3).build();
+    ForyJson nestedJson = newJsonBuilder().maxDepth(3).build();
     assertEquals(
         nestedJson
             .fromJson("{\"children\":[{\"value\":2}]}", DepthNode.class)
@@ -91,7 +97,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
 
   @Test
   public void readJsonObjectMaxDepth() {
-    ForyJson json = ForyJson.builder().maxDepth(2).build();
+    ForyJson json = newJsonBuilder().maxDepth(2).build();
     JSONObject object = json.fromJson("{\"items\":[1]}", JSONObject.class);
     assertTrue(object.get("items") instanceof JSONArray);
     assertThrows(
@@ -101,7 +107,7 @@ public class JsonDepthTest extends ForyJsonTestModels {
 
   @Test
   public void readDepthReset() {
-    ForyJson json = ForyJson.builder().maxDepth(1).build();
+    ForyJson json = newJsonBuilder().maxDepth(1).build();
     assertThrows(
         ForyJsonException.class, () -> 
json.fromJson("{\"child\":{\"value\":1}}", DepthNode.class));
     assertEquals(json.fromJson("{\"value\":2}", DepthNode.class).value, 2);
@@ -116,6 +122,6 @@ public class JsonDepthTest extends ForyJsonTestModels {
 
   @Test
   public void rejectInvalidMaxDepth() {
-    assertThrows(IllegalArgumentException.class, () -> 
ForyJson.builder().maxDepth(0));
+    assertThrows(IllegalArgumentException.class, () -> 
newJsonBuilder().maxDepth(0));
   }
 }
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java
index e693758f4..8cf5f57e6 100644
--- 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java
+++ 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGeneratedCodecTest.java
@@ -23,6 +23,7 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
@@ -42,14 +43,20 @@ import org.apache.fory.json.meta.JsonFieldNameHash;
 import org.apache.fory.json.reader.Latin1JsonReader;
 import org.apache.fory.json.reader.Utf8JsonReader;
 import org.apache.fory.json.resolver.JsonTypeResolver;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonGeneratedCodecTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonGeneratedCodecTest(boolean codegen) {
+    super(codegen);
+  }
+
   private static final String GENERATED_SUFFIX = "ForyJsonCodec";
 
   @Test
   public void writeRecursiveGeneratedTypes() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     RecursiveParent value = new RecursiveParent();
     assertEquals(json.toJson(value), 
"{\"child\":{\"name\":\"child\"},\"name\":\"parent\"}");
     assertEquals(
@@ -61,7 +68,7 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
 
   @Test
   public void writeGeneratedTokenChanges() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     TokenValues value = new TokenValues();
     String first = 
"{\"count\":1,\"name\":\"alpha\",\"tags\":[\"x\",\"y\"],\"total\":2}";
     assertEquals(json.toJson(value), first);
@@ -80,7 +87,7 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
 
   @Test
   public void writeGeneratedTokenLanes() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     TokenGroup group = new TokenGroup();
     group.values =
         Arrays.asList(
@@ -112,7 +119,7 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
 
   @Test
   public void readGeneratedObjectCollection() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String input = 
"{\"values\":[{\"count\":1,\"name\":\"alpha\",\"tags\":[\"x\"],\"total\":2}]}";
     TokenGroup stringValue = json.fromJson(input, TokenGroup.class);
     TokenGroup utf8Value = 
json.fromJson(input.getBytes(StandardCharsets.UTF_8), TokenGroup.class);
@@ -127,7 +134,7 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
 
   @Test
   public void readGeneratedCollectionFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String input =
         "{\"kinds\":[\"FAST\",\"SMALL\"],\"names\":[\"alpha\",\"你好,Fory\"]," + 
"\"numbers\":[1,2]}";
     assertGeneratedCollections(json.fromJson(input, 
GeneratedCollectionFields.class));
@@ -138,24 +145,38 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
 
   @Test
   public void sameConfigUsesSameId() throws Exception {
-    ForyJson first = ForyJson.builder().build();
-    ForyJson second = ForyJson.builder().build();
-    ForyJson writeNullFields = 
ForyJson.builder().writeNullFields(true).build();
+    ForyJson first = newJson();
+    ForyJson second = newJson();
+    ForyJson writeNullFields = newJsonBuilder().writeNullFields(true).build();
     first.toJsonBytes(new PublicFields());
     second.toJsonBytes(new PublicFields());
     writeNullFields.toJsonBytes(new PublicFields());
+    if (!codegenEnabled()) {
+      assertFalse(first.hasGeneratedWriter(PublicFields.class));
+      assertFalse(second.hasGeneratedWriter(PublicFields.class));
+      assertFalse(writeNullFields.hasGeneratedWriter(PublicFields.class));
+      return;
+    }
 
     Class<?> firstWriterClass = generatedUtf8WriterClass(first, 
PublicFields.class);
     Class<?> secondWriterClass = generatedUtf8WriterClass(second, 
PublicFields.class);
     Class<?> writeNullWriterClass = generatedUtf8WriterClass(writeNullFields, 
PublicFields.class);
+    ForyJson asyncDefault = ForyJson.builder().withCodegen(true).build();
+    asyncDefault.toJsonBytes(new PublicFields());
+    awaitGenerated(asyncDefault, PublicFields.class);
+    Class<?> asyncWriterClass = generatedUtf8WriterClass(asyncDefault, 
PublicFields.class);
     assertEquals(
         firstWriterClass.getPackage().getName(), 
PublicFields.class.getPackage().getName());
     assertEquals(
         secondWriterClass.getPackage().getName(), 
PublicFields.class.getPackage().getName());
+    assertEquals(
+        asyncWriterClass.getPackage().getName(), 
PublicFields.class.getPackage().getName());
     assertGeneratedName(firstWriterClass, PublicFields.class, "Utf8");
     assertGeneratedName(secondWriterClass, PublicFields.class, "Utf8");
     assertGeneratedName(writeNullWriterClass, PublicFields.class, "Utf8");
+    assertGeneratedName(asyncWriterClass, PublicFields.class, "Utf8");
     assertEquals(generatedId(secondWriterClass), 
generatedId(firstWriterClass));
+    assertEquals(generatedId(asyncWriterClass), generatedId(firstWriterClass));
     assertNotEquals(generatedId(writeNullWriterClass), 
generatedId(firstWriterClass));
   }
 
@@ -200,7 +221,7 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
 
   @Test
   public void readGeneratedLongAsciiFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String input =
         
"{\"registered\":\"today\",\"longitude\":12.5,\"favoriteFruit\":\"apple\","
             + "\"shortName\":\"core\"}";
@@ -212,7 +233,7 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
 
   @Test
   public void readSplitGeneratedFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String ordered =
         
"{\"f0\":0,\"f1\":\"one\",\"f2\":2,\"f3\":\"three\",\"f4\":4,\"f5\":\"five\","
             + "\"f6\":6,\"f7\":\"seven\",\"f8\":8,\"f9\":\"nine\",\"f10\":10,"
@@ -300,6 +321,16 @@ public class JsonGeneratedCodecTest extends 
ForyJsonTestModels {
     return utf8WriterField.get(codec).getClass();
   }
 
+  private static void awaitGenerated(ForyJson json, Class<?> type) throws 
InterruptedException {
+    for (int i = 0; i < 200; i++) {
+      if (json.hasGeneratedWriter(type)) {
+        return;
+      }
+      Thread.sleep(10);
+    }
+    fail("Timed out waiting for generated JSON codec for " + type);
+  }
+
   private static void assertGeneratedName(
       Class<?> generatedClass, Class<?> valueType, String role) {
     String simpleName = generatedClass.getSimpleName();
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
index b92c9aa95..327a1134c 100644
--- 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
+++ 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonGuavaOptionalDependencyTest.java
@@ -31,16 +31,24 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.fory.platform.JdkVersion;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
-public class JsonGuavaOptionalDependencyTest {
+public class JsonGuavaOptionalDependencyTest extends ForyJsonTestModels {
   private static final String RESULT = "RESULT:ok";
 
+  @Factory(dataProvider = "codegen")
+  public JsonGuavaOptionalDependencyTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void buildWithoutGuava() throws Exception {
     String filteredClassPath = 
removeGuavaFromClasspath(System.getProperty("java.class.path"));
     Process process =
-        new ProcessBuilder(javaCommand(filteredClassPath, NoGuavaMain.class))
+        new ProcessBuilder(
+                javaCommand(
+                    filteredClassPath, NoGuavaMain.class, 
Boolean.toString(codegenEnabled())))
             .redirectErrorStream(true)
             .start();
     String output = readFully(process.getInputStream());
@@ -48,7 +56,7 @@ public class JsonGuavaOptionalDependencyTest {
     assertTrue(output.contains(RESULT), output);
   }
 
-  private static List<String> javaCommand(String classPath, Class<?> 
mainClass) {
+  private static List<String> javaCommand(String classPath, Class<?> 
mainClass, String codegen) {
     List<String> command =
         new java.util.ArrayList<>(
             Arrays.asList(
@@ -63,6 +71,7 @@ public class JsonGuavaOptionalDependencyTest {
     command.add("-cp");
     command.add(classPath);
     command.add(mainClass.getName());
+    command.add(codegen);
     return command;
   }
 
@@ -87,7 +96,8 @@ public class JsonGuavaOptionalDependencyTest {
       assertNotLoadable("com.google.common.collect.ImmutableList");
       assertNotLoadable("com.google.common.primitives.ImmutableIntArray");
 
-      ForyJson json = ForyJson.builder().build();
+      boolean codegen = Boolean.parseBoolean(args[0]);
+      ForyJson json = 
ForyJson.builder().withCodegen(codegen).withAsyncCompilation(false).build();
       PlainValue stringValue = 
json.fromJson("{\"name\":\"string\",\"count\":7}", PlainValue.class);
       assertValue(stringValue, "string", 7);
 
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java
index 3ed61ac7e..d604d7253 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonObjectTest.java
@@ -40,12 +40,18 @@ import org.apache.fory.json.data.MethodsIgnored;
 import org.apache.fory.json.data.ParentValue;
 import org.apache.fory.json.data.PrivateFields;
 import org.apache.fory.json.data.PublicFields;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonObjectTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonObjectTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void writePublicFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new PublicFields()), 
"{\"active\":true,\"id\":7,\"name\":\"fory\"}");
     assertEquals(
         new String(json.toJsonBytes(new PublicFields()), 
StandardCharsets.UTF_8),
@@ -54,7 +60,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void writeJsonToOutputStream() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     ByteArrayOutputStream output = new ByteArrayOutputStream();
     json.writeJsonTo(new PublicFields(), output);
     assertEquals(
@@ -82,7 +88,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void writeFirstIntGenerated() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected = "{\"count\":2,\"name\":\"first\"}";
     assertEquals(json.toJson(new FirstIntField()), expected);
     assertEquals(
@@ -92,7 +98,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void sharedFacadeThreads() throws Exception {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected = "{\"active\":true,\"id\":7,\"name\":\"fory\"}";
     int threads = 8;
     int iterations = 200;
@@ -129,14 +135,14 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void useGeneratedWriter() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new PublicFields()), 
"{\"active\":true,\"id\":7,\"name\":\"fory\"}");
     assertGeneratedWhenSupported(json, PublicFields.class);
   }
 
   @Test
-  public void disableGeneratedWriter() {
-    ForyJson json = ForyJson.builder().withCodegen(false).build();
+  public void generatedWriterSetting() {
+    ForyJson json = newJson();
     assertEquals(json.toJson(new PublicFields()), 
"{\"active\":true,\"id\":7,\"name\":\"fory\"}");
     PublicFields fields =
         json.fromJson("{\"active\":false,\"id\":8,\"name\":\"json\"}", 
PublicFields.class);
@@ -150,12 +156,12 @@ public class JsonObjectTest extends ForyJsonTestModels {
             BoxedScalars.class);
     assertEquals(scalars.byteValue, Byte.valueOf((byte) 2));
     assertEquals(scalars.shortValue, Short.valueOf((short) 3));
-    assertEquals(json.hasGeneratedWriter(PublicFields.class), false);
+    assertGeneratedWhenSupported(json, PublicFields.class);
   }
 
   @Test
   public void writeNullFields() {
-    ForyJson json = ForyJson.builder().writeNullFields(true).build();
+    ForyJson json = newJsonBuilder().writeNullFields(true).build();
     assertEquals(
         json.toJson(new PublicFields()),
         "{\"active\":true,\"id\":7,\"name\":\"fory\",\"missing\":null}");
@@ -163,7 +169,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void fieldOnlyModeIgnoresMethods() {
-    ForyJson json = ForyJson.builder().withFieldMode(true).build();
+    ForyJson json = newJsonBuilder().withFieldMode(true).build();
     assertEquals(
         json.toJson(new MethodsIgnored()),
         "{\"setterCalls\":0,\"value\":\"field\",\"hidden\":\"hidden\"}");
@@ -176,7 +182,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void writeDeclaredFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected = "{\"id\":11,\"name\":\"private\"}";
     assertEquals(json.toJson(new PrivateFields()), expected);
     assertEquals(
@@ -193,13 +199,13 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void writeDirectionalIgnore() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new DirectionalIgnore()), "{\"writeOnly\":2}");
   }
 
   @Test
   public void writeDeclaredObjectFieldType() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected = "{\"value\":{\"parent\":1}}";
     assertEquals(json.toJson(new DeclaredParentField()), expected);
     assertEquals(
@@ -212,7 +218,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void readPublicFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     PublicFields fields =
         json.fromJson(
             
"{\"unknown\":[1,true,{\"x\":\"y\"}],\"name\":\"fory\",\"id\":7,\"active\":true}",
@@ -224,7 +230,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void readUtf8Bytes() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     byte[] bytes =
         "{\"name\":\"\uD83D\uDE00\u1234\",\"id\":8,\"active\":false}"
             .getBytes(StandardCharsets.UTF_8);
@@ -236,7 +242,7 @@ public class JsonObjectTest extends ForyJsonTestModels {
 
   @Test
   public void readDirectionalIgnore() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     DirectionalIgnore value =
         json.fromJson("{\"both\":7,\"writeOnly\":8,\"readOnly\":9}", 
DirectionalIgnore.class);
     assertEquals(value.both, 1);
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java
index ca3991dcf..9fe145e3e 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonPropertyTest.java
@@ -35,19 +35,25 @@ import org.apache.fory.json.data.BeanProperties.MixedBean;
 import org.apache.fory.json.data.BeanProperties.OverloadedSetterBean;
 import org.apache.fory.json.data.BeanProperties.SetterBean;
 import org.apache.fory.json.data.BeanProperties.SetterOnlyBean;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonPropertyTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonPropertyTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void writePrivateGetters() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new GetterBean()), 
"{\"id\":17,\"name\":\"getter-field\"}");
     assertGeneratedWhenSupported(json, GetterBean.class);
   }
 
   @Test
   public void readPrivateSetters() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     SetterBean value = json.fromJson("{\"id\":4,\"name\":\"alpha\"}", 
SetterBean.class);
     assertEquals(SetterBean.id(value), 5);
     assertEquals(SetterBean.name(value), "set-alpha");
@@ -57,7 +63,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
 
   @Test
   public void roundTripMixedProperties() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String text = "{\"count\":3,\"name\":\"mixed\",\"score\":5}";
     assertEquals(json.toJson(new MixedBean()), text);
     assertEquals(json.toJson(json.fromJson(text, MixedBean.class)), text);
@@ -66,13 +72,13 @@ public class JsonPropertyTest extends ForyJsonTestModels {
 
   @Test
   public void writeBooleanIsGetter() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new BooleanBean()), "{\"ready\":true}");
   }
 
   @Test
   public void getterOnlyWrites() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new GetterOnlyBean()), "{\"computed\":6}");
     GetterOnlyBean value = json.fromJson("{\"computed\":99}", 
GetterOnlyBean.class);
     assertEquals(json.toJson(value), "{\"computed\":6}");
@@ -80,7 +86,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
 
   @Test
   public void setterOnlyReads() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new SetterOnlyBean()), "{}");
     SetterOnlyBean value = json.fromJson("{\"secret\":\"alpha\"}", 
SetterOnlyBean.class);
     assertEquals(SetterOnlyBean.received(value), "set-alpha");
@@ -88,7 +94,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
 
   @Test
   public void fieldOnlyMode() {
-    ForyJson json = ForyJson.builder().withFieldMode(true).build();
+    ForyJson json = newJsonBuilder().withFieldMode(true).build();
     assertEquals(json.toJson(new GetterBean()), 
"{\"id\":7,\"name\":\"field\"}");
     assertEquals(json.toJson(new GetterOnlyBean()), "{}");
     SetterOnlyBean value = json.fromJson("{\"secret\":\"alpha\"}", 
SetterOnlyBean.class);
@@ -97,7 +103,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
 
   @Test
   public void rejectPropertyConflicts() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(ForyJsonException.class, () -> json.toJson(new 
DuplicateGetterBean()));
     assertThrows(
         ForyJsonException.class,
@@ -107,7 +113,7 @@ public class JsonPropertyTest extends ForyJsonTestModels {
 
   @Test
   public void inheritedProperties() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new InheritedChild()), 
"{\"id\":4,\"name\":\"child\"}");
     InheritedChild value = json.fromJson("{\"id\":7,\"name\":\"json\"}", 
InheritedChild.class);
     assertEquals(InheritedParent.id(value), 8);
@@ -116,13 +122,13 @@ public class JsonPropertyTest extends ForyJsonTestModels {
 
   @Test
   public void ignoreInvalidAccessors() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new InvalidAccessorBean()), 
"{\"value\":\"field\"}");
   }
 
   @Test
   public void finalFieldsStayReadOnly() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new FinalFieldBean()), 
"{\"id\":1,\"name\":\"field\"}");
     FinalFieldBean value = json.fromJson("{\"id\":9,\"name\":\"json\"}", 
FinalFieldBean.class);
     assertEquals(value.id, 1);
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java
index d2b23f264..7c6c8e7c6 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonRecordTest.java
@@ -26,9 +26,15 @@ import java.util.Arrays;
 import java.util.List;
 import org.apache.fory.platform.JdkVersion;
 import org.testng.SkipException;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonRecordTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonRecordTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void writeReadRecordClass() throws Exception {
     if (JdkVersion.MAJOR_VERSION < 17) {
@@ -48,7 +54,7 @@ public class JsonRecordTest extends ForyJsonTestModels {
     Object value =
         type.getConstructor(int.class, String.class, List.class, childType)
             .newInstance(7, ZH_TEXT, Arrays.asList("a", "b"), child);
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected =
         "{\"id\":7,\"name\":\"你好,Fory\",\"tags\":[\"a\",\"b\"]," + 
"\"child\":{\"label\":\"kid\"}}";
     assertEquals(json.toJson(value), expected);
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java
index bc1c99a0a..ffb50b85f 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonScalarTest.java
@@ -83,12 +83,18 @@ import org.apache.fory.json.writer.StringJsonWriter;
 import org.apache.fory.json.writer.Utf8JsonWriter;
 import org.apache.fory.reflect.TypeRef;
 import org.apache.fory.serializer.StringSerializer;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonScalarTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonScalarTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void writeBoxedScalars() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected =
         
"{\"bool\":true,\"byteValue\":2,\"charValue\":\"x\",\"doubleValue\":2.5,"
             + 
"\"floatValue\":1.5,\"intValue\":4,\"longValue\":5,\"shortValue\":3}";
@@ -126,7 +132,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadNonFiniteFloats() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(Double.NaN), "\"NaN\"");
     assertEquals(json.toJson(Double.POSITIVE_INFINITY), "\"Infinity\"");
     assertEquals(json.toJson(Double.NEGATIVE_INFINITY), "\"-Infinity\"");
@@ -189,7 +195,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeNaturalObjectValues() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected =
         
"{\"bool\":true,\"list\":[\"a\",1,false],\"map\":{\"name\":\"fory\",\"score\":9},"
             + "\"number\":7,\"text\":\"fory\"}";
@@ -200,7 +206,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeNaturalEmptyObject() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String expected = "{\"value\":{}}";
     assertEquals(json.toJson(new NaturalObjectValue()), expected);
     assertEquals(
@@ -209,7 +215,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readBoxedScalars() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     BoxedScalars value =
         json.fromJson(
             
"{\"bool\":false,\"byteValue\":6,\"charValue\":\"z\",\"doubleValue\":3.5,"
@@ -227,7 +233,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readNumericBoundaries() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String latin1 =
         "{\"intMax\":2147483647,\"intMin\":-2147483648,"
             + 
"\"longMax\":9223372036854775807,\"longMin\":-9223372036854775808,"
@@ -329,7 +335,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeNumericBoundaries() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     NumericBoundaries value = new NumericBoundaries();
     value.intMax = Integer.MAX_VALUE;
     value.intMin = Integer.MIN_VALUE;
@@ -360,7 +366,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
     assertEquals(
         writer.toJson(), "[\"" + ZH_TEXT + 
"\",9223372036854775807,2147483648,-2147483648]");
 
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Utf16NumericFields value = new Utf16NumericFields();
     value.prefix = ZH_TEXT;
     value.zero = 0;
@@ -392,7 +398,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadCoreScalarFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     CoreScalarFields value = new CoreScalarFields();
     String expected =
         
"{\"atomicInt\":7,\"bigDecimal\":12345.6789,\"bigInteger\":12345678901234567890,"
@@ -427,7 +433,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadAtomicScalars() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.toJson(new AtomicBoolean(true)), "true");
     assertEquals(json.fromJson("false", AtomicBoolean.class).get(), false);
     assertEquals(json.toJson(new AtomicInteger(12)), "12");
@@ -446,7 +452,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeUtf8ScalarFormats() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     UUID uuid = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
     assertEquals(
         new String(json.toJsonBytes(uuid), StandardCharsets.UTF_8),
@@ -491,7 +497,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeGeneratedUtf8Scalars() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Utf8ScalarFields fields = new Utf8ScalarFields();
     fields.uuid = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
     fields.decimal = new BigDecimal("12345.6789");
@@ -509,7 +515,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeCommonScalarFastFormats() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     UUID uuid = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
     assertEquals(json.toJson(new StringBuilder("build")), "\"build\"");
     assertEquals(
@@ -552,7 +558,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readScalarRoots() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.fromJson("7", int.class), Integer.valueOf(7));
     assertEquals(json.fromJson("true", boolean.class), Boolean.TRUE);
     assertEquals(json.fromJson("0.100", BigDecimal.class), new 
BigDecimal("0.100"));
@@ -579,7 +585,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readCommonScalarFastFormats() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.fromJson("123456789", BigInteger.class), 
BigInteger.valueOf(123456789L));
     assertEquals(json.fromJson("123456789", BigDecimal.class), new 
BigDecimal("123456789"));
     assertEquals(
@@ -649,7 +655,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadSqlTimeScalars() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     java.sql.Date date = new java.sql.Date(123456789L);
     Time time = new Time(234567890L);
     Timestamp timestamp = new Timestamp(345678901L);
@@ -663,7 +669,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readDoubleDecimalContainers() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     byte[] doublesJson = "[12.5,-0.0,1.25e2]".getBytes(StandardCharsets.UTF_8);
     double[] doubles = json.fromJson(doublesJson, double[].class);
     assertEquals(doubles, new double[] {12.5d, -0.0d, 125.0d});
@@ -700,7 +706,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readGeneratedUtf8BigDecimal() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     byte[] input =
         ("{\"uuid\":\"123e4567-e89b-12d3-a456-426614174000\","
                 + "\"decimal\":0.12345678901234567,"
@@ -714,7 +720,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readGeneratedLatin1Scalars() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String input =
         "{\"uuid\":\"123e4567-e89b-12d3-a456-426614174000\","
             + "\"decimal\":0.12345678901234567,"
@@ -730,7 +736,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readUntypedLargeInteger() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     BigInteger unsigned = new BigInteger("18446744073709550616");
     assertEquals(json.fromJson(unsigned.toString(), Object.class), unsigned);
     JSONObject object = json.fromJson("{\"count\":18446744073709550616}", 
JSONObject.class);
@@ -739,7 +745,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadDeclaredNumber() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.fromJson("7", Number.class), Long.valueOf(7));
     assertEquals(
         json.fromJson("9223372036854775808", Number.class), new 
BigInteger("9223372036854775808"));
@@ -764,7 +770,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadCharSequence() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     CharSequence root = json.fromJson("\"fory\"", CharSequence.class);
     assertEquals(root, "fory");
     assertEquals(root.getClass(), String.class);
@@ -780,7 +786,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadBitSet() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     BitSet empty = new BitSet();
     assertEquals(json.toJson(empty), "[]");
     assertEquals(json.fromJson("[]", BitSet.class), empty);
@@ -807,7 +813,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadChronoDates() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     LocalDate iso = LocalDate.of(2024, 2, 3);
     ChronoDateFields fields = new ChronoDateFields();
     fields.hijrah = HijrahChronology.INSTANCE.date(iso);
@@ -831,7 +837,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void rejectNetworkAddressTypes() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(ForyJsonException.class, () -> 
json.toJson(InetAddress.getLoopbackAddress()));
     assertThrows(
         ForyJsonException.class,
@@ -844,7 +850,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void rejectClassTypeByDefault() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(ForyJsonException.class, () -> json.toJson(String.class));
     assertThrows(ForyJsonException.class, () -> json.toJson(int.class));
     assertThrows(ForyJsonException.class, () -> 
json.fromJson("\"java.lang.String\"", Class.class));
@@ -853,7 +859,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void rejectClassFields() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(ForyJsonException.class, () -> json.toJson(new 
ClassFieldHolder()));
     assertThrows(
         ForyJsonException.class,
@@ -862,7 +868,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void rejectClassArrays() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(ForyJsonException.class, () -> json.toJson(new Class<?>[] 
{String.class}));
     assertThrows(
         ForyJsonException.class, () -> json.fromJson("[\"java.lang.String\"]", 
Class[].class));
@@ -874,7 +880,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadFileAndPath() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     File file = new File("fory-json-file.txt");
     Path path = Paths.get("fory-json-path.txt");
     assertEquals(json.toJson(file), "\"fory-json-file.txt\"");
@@ -894,7 +900,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readLocalDateFromDateTime() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     LocalDate expected = LocalDate.of(2023, 7, 2);
     assertEquals(json.fromJson("\"2023-07-02T16:00:00.000Z\"", 
LocalDate.class), expected);
     assertEquals(
@@ -908,7 +914,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readLocalDateFallbackForms() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     LocalDate extended = LocalDate.of(10000, 2, 3);
     assertEquals(json.fromJson("\"+10000-02-03\"", LocalDate.class), extended);
     assertEquals(
@@ -920,7 +926,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readOffsetDateTime() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     OffsetDateTime utc = OffsetDateTime.of(2024, 2, 3, 4, 5, 6, 0, 
ZoneOffset.UTC);
     assertEquals(json.fromJson("\"2024-02-03T04:05:06Z\"", 
OffsetDateTime.class), utc);
     assertEquals(
@@ -946,7 +952,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void readOffsetDateTimeFallbackForms() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     OffsetDateTime extended = OffsetDateTime.of(10000, 2, 3, 4, 5, 6, 0, 
ZoneOffset.ofHours(8));
     String input = "\"+10000-02-03T04:05:06+08:00\"";
     assertEquals(json.fromJson(input, OffsetDateTime.class), extended);
@@ -971,7 +977,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
     assertEquals(timestampReader.readIsoOffsetDateTime(), timestamp);
     timestampReader.finish();
 
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     Utf16TemporalFields fields =
         json.fromJson(
             "{\"text\":\"中文\",\"date\":\"2023-07-02\","
@@ -985,16 +991,16 @@ public class JsonScalarTest extends ForyJsonTestModels {
   @Test
   public void objectFieldUsesUtf8Codec() {
     ForyJson json =
-        ForyJson.builder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
+        newJsonBuilder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
     ModeAwareHolder holder =
         json.fromJson("{\"value\":{}}".getBytes(StandardCharsets.UTF_8), 
ModeAwareHolder.class);
-    assertEquals(holder.value.mode, "utf8");
+    assertEquals(holder.value.mode, codegenEnabled() ? "utf8" : "generic");
   }
 
   @Test
   public void byteInputUsesUtf8Codec() {
     ForyJson json =
-        ForyJson.builder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
+        newJsonBuilder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
     ModeAwareValue value =
         json.fromJson("{}".getBytes(StandardCharsets.UTF_8), 
ModeAwareValue.class);
     assertEquals(value.mode, "utf8");
@@ -1003,7 +1009,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
   @Test
   public void stringInputUsesLatin1Codec() {
     ForyJson json =
-        ForyJson.builder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
+        newJsonBuilder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
     ModeAwareValue value = json.fromJson("{}", ModeAwareValue.class);
     String expected = StringSerializer.isBytesBackedString() ? "latin1" : 
"utf16";
     assertEquals(value.mode, expected);
@@ -1012,14 +1018,14 @@ public class JsonScalarTest extends ForyJsonTestModels {
   @Test
   public void stringInputUsesUtf16Codec() {
     ForyJson json =
-        ForyJson.builder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
+        newJsonBuilder().registerCodec(ModeAwareValue.class, new 
ModeAwareCodec()).build();
     ModeAwareValue value = json.fromJson("{\"ignored\":\"\u0100\"}", 
ModeAwareValue.class);
     assertEquals(value.mode, "utf16");
   }
 
   @Test
   public void rejectMalformedStringScalar() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(
         RuntimeException.class,
         () -> json.fromJson("\"2024-02-03 04:05:06\"", LocalDateTime.class));
@@ -1027,7 +1033,7 @@ public class JsonScalarTest extends ForyJsonTestModels {
 
   @Test
   public void rejectLeadingZero() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertThrows(ForyJsonException.class, () -> json.fromJson("01", 
int.class));
     assertThrows(
         ForyJsonException.class,
diff --git 
a/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java 
b/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java
index 3caf82909..6f14b5dd2 100644
--- a/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java
+++ b/java/fory-json/src/test/java/org/apache/fory/json/JsonStringTest.java
@@ -41,12 +41,18 @@ import org.apache.fory.json.reader.Utf16JsonReader;
 import org.apache.fory.json.writer.StringJsonWriter;
 import org.apache.fory.memory.NativeByteOrder;
 import org.apache.fory.serializer.StringSerializer;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 public class JsonStringTest extends ForyJsonTestModels {
+  @Factory(dataProvider = "codegen")
+  public JsonStringTest(boolean codegen) {
+    super(codegen);
+  }
+
   @Test
   public void escapeStrings() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     PublicFields fields = new PublicFields();
     fields.name = "a\n\"b\"\\\u1234";
     String stringExpected = 
"{\"active\":true,\"id\":7,\"name\":\"a\\n\\\"b\\\"\\\\\u1234\"}";
@@ -56,7 +62,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void writeUtf16StringText() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     UnicodeValues values = new UnicodeValues();
     String expected =
         "{\"first\":\"\u1234\",\"second\":\"music \uD834\uDD1E\","
@@ -155,7 +161,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void writeUtf16Char() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     CharValue value = new CharValue();
     value.value = '\u1234';
     assertEquals(json.toJson(value), "{\"value\":\"\u1234\"}");
@@ -165,7 +171,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void writeNonLatin1Matrix() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     UnicodeMatrix value = new UnicodeMatrix();
     String expected = unicodeMatrixJson();
     assertEquals(json.toJson(value), expected);
@@ -183,7 +189,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void writeReadZhEuStrings() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertTextRoundTrip(json, ZH_TEXT);
     assertTextRoundTrip(json, EU_TEXT);
   }
@@ -241,7 +247,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void readStringInputLayouts() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String latin1Json = "{\"active\":true,\"id\":7,\"name\":\"café\"}";
     String utf16Json = "{\"active\":true,\"id\":7,\"name\":\"你好,Fory\"}";
     if (StringSerializer.isBytesBackedString()) {
@@ -256,7 +262,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void readStringInputEscapes() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
 
     String asciiEscaped = 
json.fromJson("\"line\\n\\r\\t\\b\\f\\\\\\\"\\/end\"", String.class);
     String latin1Escaped = json.fromJson("\"caf\\u00E9\"", String.class);
@@ -312,7 +318,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void readUnicodeFieldNames() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String direct = "{\"café\":\"" + EU_TEXT + "\",\"你好\":\"" + ZH_TEXT + 
"\"}";
     String escaped = "{\"caf\\u00e9\":\"" + EU_TEXT + 
"\",\"\\u4f60\\u597d\":\"" + ZH_TEXT + "\"}";
     UnicodeFieldNames directValue = json.fromJson(direct, 
UnicodeFieldNames.class);
@@ -329,7 +335,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void readUnicodeEnum() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String direct = "{\"kind\":\"你好\"}";
     String escaped = "{\"kind\":\"\\u4f60\\u597d\"}";
     String asciiDirect = "{\"kind\":\"FAST\"}";
@@ -351,7 +357,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void writeLatin1NonAsciiBytes() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     PublicFields fields = new PublicFields();
     fields.name = "caf\u00e9";
     assertEquals(json.toJson(fields), 
"{\"active\":true,\"id\":7,\"name\":\"caf\u00e9\"}");
@@ -367,7 +373,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void rejectSurrogateChar() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     CharValue value = new CharValue();
     value.value = '\uD800';
     assertThrows(ForyJsonException.class, () -> json.toJson(value));
@@ -376,7 +382,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void rejectSurrogateString() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     PublicFields fields = new PublicFields();
     fields.name = "\uD800";
     assertThrows(ForyJsonException.class, () -> json.toJson(fields));
@@ -385,7 +391,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void writeSurrogatePair() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     PublicFields fields = new PublicFields();
     fields.name = "a\uD83D\uDE00";
     String stringExpected = 
"{\"active\":true,\"id\":7,\"name\":\"a\uD83D\uDE00\"}";
@@ -397,7 +403,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void writeStringScanBoundaries() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     for (int length :
         new int[] {
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 20, 23, 24, 25, 30, 
31, 32, 33, 63, 64, 65
@@ -415,7 +421,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void readStringScanBoundaries() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     for (int length : new int[] {0, 1, 7, 8, 15, 16, 17, 23, 24, 31, 32, 33, 
63, 64, 65}) {
       String value = repeat('a', length);
       String input = "\"" + value + "\"";
@@ -448,7 +454,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void readUtf8DecodedStrings() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     String ascii = readUtf8String(json, "\"plain ascii\"");
     String latin1 = readUtf8String(json, "\"caf\u00e9\"");
     String escaped = readUtf8String(json, "\"line\\n\\t\\\\\\\"\\/end\"");
@@ -483,7 +489,7 @@ public class JsonStringTest extends ForyJsonTestModels {
 
   @Test
   public void rejectInvalidSurrogates() {
-    ForyJson json = ForyJson.builder().build();
+    ForyJson json = newJson();
     assertEquals(json.fromJson("\"\\uD834\\uDD1E\"", String.class), 
"\uD834\uDD1E");
     assertThrows(ForyJsonException.class, () -> json.fromJson("\"\\uD800\"", 
String.class));
     assertThrows(ForyJsonException.class, () -> json.fromJson("\"\\uDC00\"", 
String.class));


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


Reply via email to