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 e7c47c410 feat(java): Add ForyFeature for GraalVM Native Image (#2701)
e7c47c410 is described below

commit e7c47c410b94d5204de915c9dadf8966c8603f33
Author: zhou yong kang <[email protected]>
AuthorDate: Thu Dec 4 15:42:58 2025 +0800

    feat(java): Add ForyFeature for GraalVM Native Image (#2701)
    
    <!--
    **Thanks for contributing to Apache Fory™.**
    
    **If this is your first time opening a PR on fory, you can refer to
    
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).**
    
    Contribution Checklist
    
    - The **Apache Fory™** community has requirements on the naming of pr
    titles. You can also find instructions in
    [CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).
    
    - Apache Fory™ has a strong focus on performance. If the PR you submit
    will have an impact on performance, please benchmark it first and
    provide the benchmark result here.
    -->
    
    ## Why?
    Reflective access to private constructors in the GraalVM Native Image
    environment is limited. ObjectCreator failures return the wrong type,
    causing a ClassCastException. Lack of friendly GraalVM error messages.
    
    <!-- Describe the purpose of this PR. -->
    
    ## What does this PR do?
    Added GraalVM environment detection and special processing, optimized
    ObjectCreator implementation, and added unit tests
    
    <!-- Describe the details of this PR. -->
    
    ## Related issues
    Close #2697
    <!--
    Is there any related issue? If this PR closes them you say say
    fix/closes:
    
    - #xxxx0
    - #xxxx1
    - Fixes #xxxx2
    -->
    
    ## Does this PR introduce any user-facing change?
    
    <!--
    If any user-facing interface changes, please [open an
    issue](https://github.com/apache/fory/issues/new/choose) describing the
    need to do so and update the document if necessary.
    
    Delete section if not applicable.
    -->
    
    - [x] Does this PR introduce any public API change?
    - [x] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    <!--
    When the PR has an impact on performance (if you don't know whether the
    PR will have an impact on performance, you can submit the PR first, and
    if it will have impact on performance, the code reviewer will explain
    it), be sure to attach a benchmark data here.
    
    Delete section if not applicable.
    -->
    
    ---------
    
    Co-authored-by: Shawn Yang <[email protected]>
    Co-authored-by: chaokunyang <[email protected]>
---
 docs/guide/graalvm_guide.md                        | 228 ++++++++-------
 integration_tests/graalvm_tests/pom.xml            |  33 +--
 .../apache/fory/graalvm/FeatureTestExample.java    | 110 +++++++
 .../main/java/org/apache/fory/graalvm/Main.java    |   1 +
 .../graalvm_tests/native-image.properties          |   4 +-
 .../apache/fory/util/GraalvmSupportRecordTest.java |  70 +++++
 .../org/apache/fory/reflect/ObjectCreators.java    |   4 +-
 .../org/apache/fory/resolver/ClassResolver.java    |   3 +
 .../org/apache/fory/resolver/TypeResolver.java     |  43 +--
 .../org/apache/fory/resolver/XtypeResolver.java    |   3 +
 .../apache/fory/serializer/JdkProxySerializer.java |   7 +-
 .../fory/serializer/ObjectStreamSerializer.java    | 323 ++++++++++++++++++---
 .../java/org/apache/fory/util/GraalvmSupport.java  | 173 +++++++++++
 .../org/apache/fory/util/function/Functions.java   |  39 ++-
 .../fory-core/native-image.properties              |   3 +
 .../fory-core/serialization-config.json            |  11 +-
 java/fory-graalvm-feature/pom.xml                  |  91 ++++++
 .../fory/graalvm/feature/ForyGraalVMFeature.java   | 115 ++++++++
 .../org.graalvm.nativeimage.hosted.Feature         |   1 +
 .../graalvm/feature/ForyGraalVMFeatureTest.java    |  78 +++++
 java/pom.xml                                       |  49 ++--
 21 files changed, 1168 insertions(+), 221 deletions(-)

diff --git a/docs/guide/graalvm_guide.md b/docs/guide/graalvm_guide.md
index 3f85c2c70..2a1f6468f 100644
--- a/docs/guide/graalvm_guide.md
+++ b/docs/guide/graalvm_guide.md
@@ -21,179 +21,207 @@ license: |
 
 ## GraalVM Native Image
 
-GraalVM `native image` can compile java code into native code ahead to build 
faster, smaller, leaner applications.
-The native image doesn't have a JIT compiler to compile bytecode into machine 
code, and doesn't support
-reflection unless configure reflection file.
+GraalVM `native image` compiles Java code into native executables 
ahead-of-time, resulting in faster startup and lower memory usage. However, 
native images don't support runtime JIT compilation or reflection without 
explicit configuration.
 
-Apache Fory™ runs on GraalVM native image pretty well. Apache Fory™ generates 
all serializer code for `Fory JIT framework` and 
`MethodHandle/LambdaMetafactory` at graalvm build time. Then use those 
generated code for serialization at runtime without
-any extra cost, the performance is great.
+Apache Fory™ works excellently with GraalVM native image by using **codegen 
instead of reflection**. All serializer code is generated at build time, 
eliminating the need for reflection configuration files in most cases.
 
-In order to use Apache Fory™ on graalvm native image, you must create Fory as 
an **static** field of a class, and **register** all classes at
-the enclosing class initialize time. Then configure `native-image.properties` 
under
-`resources/META-INF/native-image/$xxx/native-image.properties` to tell graalvm 
to init the class at native image
-build time. For example, here we configure `org.apache.fory.graalvm.Example` 
class be init at build time:
+## How It Works
 
-```properties
-Args = --initialize-at-build-time=org.apache.fory.graalvm.Example
-```
+Fory generates serialization code at GraalVM build time when you:
 
-**The main benefit of using Fory with GraalVM is that you don't need to 
configure [reflection 
json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#specifying-reflection-metadata-in-json)
 or
-[serialization 
json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#serialization)
 for your serializable classes!** This eliminates the tedious, cumbersome and 
error-prone configuration process.
+1. Create Fory as a **static** field
+2. **Register** all classes in a static initializer
+3. Call `fory.ensureSerializersCompiled()` to compile serializers
+4. Configure the class to initialize at build time via 
`native-image.properties`
 
-Fory achieves this by using **codegen instead of reflection** - all 
serialization code is generated at build time when you call:
+**The main benefit**: You don't need to configure [reflection 
json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#specifying-reflection-metadata-in-json)
 or [serialization 
json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#serialization)
 for most serializable classes.
 
-- `fory.register(YourClass.class)` to register your classes
-- `fory.ensureSerializersCompiled()` to compile serializers at build time
+Note: Fory's `asyncCompilationEnabled` option is automatically disabled for 
GraalVM native image since runtime JIT is not supported.
 
-Note that Fory `asyncCompilationEnabled` option will be disabled automatically 
for graalvm native image since graalvm
-native image doesn't support JIT at the image run time.
+## Basic Usage
 
-## Registering Your Classes
+### Step 1: Create Fory and Register Classes
 
-If you encounter a GraalVM error like:
+```java
+import org.apache.fory.Fory;
 
-```
-Type com.example.MyClass is instantiated reflectively but was never registered
-```
+public class Example {
+  // Must be static field
+  static Fory fory;
 
-This means you need to register your class with Fory. **Do NOT add it to 
reflect-config.json** - instead, register it with Fory's codegen system:
+  static {
+    fory = Fory.builder().build();
+    fory.register(MyClass.class);
+    fory.register(AnotherClass.class);
+    // Compile all serializers at build time
+    fory.ensureSerializersCompiled();
+  }
 
-```java
-static {
-  fory = Fory.builder().build();
-  // register class
-  fory.register(MyClass.class);
-  // ensure all serializers for registered classes being compiled by fory at 
graalvm native image build time.
-  fory.ensureSerializersCompiled();
+  public static void main(String[] args) {
+    byte[] bytes = fory.serialize(new MyClass());
+    MyClass obj = (MyClass) fory.deserialize(bytes);
+  }
 }
 ```
 
-And make sure the class executing static registering is initialized at build 
time in `native-image.properties`:
+### Step 2: Configure Build-Time Initialization
+
+Create 
`resources/META-INF/native-image/your-group/your-artifact/native-image.properties`:
 
 ```properties
-Args = --initialize-at-build-time=com.example.MyForySerializer
+Args = --initialize-at-build-time=com.example.Example
 ```
 
-This approach is much more efficient than reflection-based serialization and 
eliminates the need for complex GraalVM configuration files.
+## ForyGraalVMFeature (Optional)
 
-## Not thread-safe Fory
+For most types with public constructors, the basic setup above is sufficient. 
However, some advanced cases require reflection registration:
 
-Example:
+- **Private constructors** (classes without accessible no-arg constructor)
+- **Private inner classes/records**
+- **Dynamic proxy serialization**
 
-```java
-import org.apache.fory.Fory;
-import org.apache.fory.util.Preconditions;
+The `fory-graalvm-feature` module automatically handles these cases, 
eliminating the need for manual `reflect-config.json` configuration.
+
+### Adding the Dependency
 
-import java.util.List;
-import java.util.Map;
+```xml
+<dependency>
+  <groupId>org.apache.fory</groupId>
+  <artifactId>fory-graalvm-feature</artifactId>
+  <version>${fory.version}</version>
+</dependency>
+```
+
+### Enabling the Feature
 
+Add to your `native-image.properties`:
+
+```properties
+Args = --initialize-at-build-time=com.example.Example \
+       --features=org.apache.fory.graalvm.feature.ForyGraalVMFeature
+```
+
+### What ForyGraalVMFeature Handles
+
+| Scenario                        | Without Feature              | With 
Feature       |
+| ------------------------------- | ---------------------------- | 
------------------ |
+| Public classes with no-arg ctor | ✅ Works                     | ✅ Works      
     |
+| Private constructors            | ❌ Needs reflect-config.json | ✅ 
Auto-registered |
+| Private inner records           | ❌ Needs reflect-config.json | ✅ 
Auto-registered |
+| Dynamic proxies                 | ❌ Needs manual config       | ✅ 
Auto-registered |
+
+### Example with Private Record
+
+```java
 public class Example {
-  public record Record (
-    int f1,
-    String f2,
-    List<String> f3,
-    Map<String, Long> f4) {
-  }
+  // Private inner record - requires ForyGraalVMFeature
+  private record PrivateRecord(int id, String name) {}
 
   static Fory fory;
 
   static {
     fory = Fory.builder().build();
-    // register class
-    fory.register(Record.class);
-    // ensure all serializers for registered classes being compiled by fory at 
graalvm native image build time.
+    fory.register(PrivateRecord.class);
     fory.ensureSerializersCompiled();
   }
-
-  public static void main(String[] args) {
-    Record record = new Record(10, "abc", List.of("str1", "str2"), 
Map.of("k1", 10L, "k2", 20L));
-    System.out.println(record);
-    byte[] bytes = fory.serialize(record);
-    Object o = fory.deserialize(bytes);
-    System.out.println(o);
-    Preconditions.checkArgument(record.equals(o));
-  }
 }
 ```
 
-Then add `org.apache.fory.graalvm.Example` build time init to 
`native-image.properties` configuration:
+### Example with Dynamic Proxy
 
-```properties
-Args = --initialize-at-build-time=org.apache.fory.graalvm.Example
+```java
+import org.apache.fory.util.GraalvmSupport;
+
+public class ProxyExample {
+  public interface MyService {
+    String execute();
+  }
+
+  static Fory fory;
+
+  static {
+    fory = Fory.builder().build();
+    // Register proxy interface for serialization
+    GraalvmSupport.registerProxySupport(MyService.class);
+    fory.ensureSerializersCompiled();
+  }
+}
 ```
 
-## Thread-safe Fory
+## Thread-Safe Fory
+
+For multi-threaded applications, use `ThreadLocalFory`:
 
 ```java
 import org.apache.fory.Fory;
 import org.apache.fory.ThreadLocalFory;
 import org.apache.fory.ThreadSafeFory;
-import org.apache.fory.util.Preconditions;
-
-import java.util.List;
-import java.util.Map;
 
 public class ThreadSafeExample {
-  public record Foo (
-    int f1,
-    String f2,
-    List<String> f3,
-    Map<String, Long> f4) {
-  }
+  public record Foo(int f1, String f2, List<String> f3) {}
 
   static ThreadSafeFory fory;
 
   static {
     fory = new ThreadLocalFory(classLoader -> {
       Fory f = Fory.builder().build();
-      // register class
       f.register(Foo.class);
-      // ensure all serializers for registered classes being compiled by fory 
at graalvm native image build time.
-      fory.ensureSerializersCompiled();
+      f.ensureSerializersCompiled();
       return f;
     });
   }
 
   public static void main(String[] args) {
-    System.out.println(fory.deserialize(fory.serialize("abc")));
-    System.out.println(fory.deserialize(fory.serialize(List.of(1,2,3))));
-    System.out.println(fory.deserialize(fory.serialize(Map.of("k1", 1, "k2", 
2))));
-    Foo foo = new Foo(10, "abc", List.of("str1", "str2"), Map.of("k1", 10L, 
"k2", 20L));
-    System.out.println(foo);
+    Foo foo = new Foo(10, "abc", List.of("str1", "str2"));
     byte[] bytes = fory.serialize(foo);
-    Object o = fory.deserialize(bytes);
-    System.out.println(o);
+    Foo result = (Foo) fory.deserialize(bytes);
   }
 }
 ```
 
-Then add `org.apache.fory.graalvm.ThreadSafeExample` build time init to 
`native-image.properties` configuration:
+## Troubleshooting
 
-```properties
-Args = --initialize-at-build-time=org.apache.fory.graalvm.ThreadSafeExample
+### "Type is instantiated reflectively but was never registered"
+
+If you see this error:
+
+```
+Type com.example.MyClass is instantiated reflectively but was never registered
 ```
 
-## Framework Integration
+**Solution**: Register the class with Fory (don't add to reflect-config.json):
 
-For framework developers, if you want to integrate fory for serialization, you 
can provided a configuration file to let
-the users to list all the classes they want to serialize, then you can load 
those classes and invoke
-`org.apache.fory.Fory.register(Class<?>)` to register those classes in your 
Fory integration class. After all classes are registered, you need to invoke 
`org.apache.fory.Fory.ensureSerializersCompiled()` to compile serializers at 
build time, and configure that class be initialized at graalvm native image 
build time.
+```java
+fory.register(MyClass.class);
+fory.ensureSerializersCompiled();
+```
 
-## Benchmark
+If the class has a private constructor, either:
 
-Here we give two class benchmarks between Fory and Graalvm Serialization.
+1. Add `fory-graalvm-feature` dependency, or
+2. Create a `reflect-config.json` for that specific class
 
-When Fory compression is disabled:
+## Framework Integration
 
-- Struct: Fory is `46x speed, 43% size` compared to JDK.
-- Pojo: Fory is `12x speed, 56% size` compared to JDK.
+For framework developers integrating Fory:
+
+1. Provide a configuration file for users to list serializable classes
+2. Load those classes and call `fory.register(Class<?>)` for each
+3. Call `fory.ensureSerializersCompiled()` after all registrations
+4. Configure your integration class for build-time initialization
+
+## Benchmark
 
-When Fory compression is enabled:
+Performance comparison between Fory and GraalVM JDK Serialization:
 
-- Struct: Fory is `24x speed, 31% size` compared to JDK.
-- Pojo: Fory is `12x speed, 48% size` compared to JDK.
+| Type   | Compression | Speed      | Size |
+| ------ | ----------- | ---------- | ---- |
+| Struct | Off         | 46x faster | 43%  |
+| Struct | On          | 24x faster | 31%  |
+| Pojo   | Off         | 12x faster | 56%  |
+| Pojo   | On          | 12x faster | 48%  |
 
-See 
[[Benchmark.java](https://github.com/apache/fory/blob/main/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Benchmark.java)]
 for benchmark code.
+See 
[Benchmark.java](https://github.com/apache/fory/blob/main/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Benchmark.java)
 for benchmark code.
 
 ### Struct Benchmark
 
diff --git a/integration_tests/graalvm_tests/pom.xml 
b/integration_tests/graalvm_tests/pom.xml
index f6eed86cc..5f094a9db 100644
--- a/integration_tests/graalvm_tests/pom.xml
+++ b/integration_tests/graalvm_tests/pom.xml
@@ -35,7 +35,6 @@
     <maven.compiler.target>17</maven.compiler.target>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <native.maven.plugin.version>0.9.28</native.maven.plugin.version>
-    <junit.jupiter.version>5.8.1</junit.jupiter.version>
     <mainClass>org.apache.fory.graalvm.Main</mainClass>
     <imageName>main</imageName>
   </properties>
@@ -59,6 +58,16 @@
       <artifactId>fory-core</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.fory</groupId>
+      <artifactId>fory-graalvm-feature</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.testng</groupId>
+      <artifactId>testng</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -149,20 +158,14 @@
             <version>${native.maven.plugin.version}</version>
             <extensions>true</extensions>
             <executions>
+              <!-- Build native image (no fork) to align with CI behavior -->
               <execution>
                 <id>build-native</id>
                 <goals>
-                  <goal>build</goal>
+                  <goal>compile-no-fork</goal>
                 </goals>
                 <phase>package</phase>
               </execution>
-              <execution>
-                <id>test-native</id>
-                <goals>
-                  <goal>test</goal>
-                </goals>
-                <phase>test</phase>
-              </execution>
             </executions>
             <configuration>
               <fallback>false</fallback>
@@ -184,17 +187,13 @@
               <execution>
                 <id>java-agent</id>
                 <goals>
-                  <goal>exec</goal>
+                  <goal>java</goal>
                 </goals>
                 <phase>test</phase>
                 <configuration>
-                  <executable>java</executable>
-                  
<workingDirectory>${project.build.directory}</workingDirectory>
-                  <arguments>
-                    <argument>-classpath</argument>
-                    <classpath/>
-                    <argument>${mainClass}</argument>
-                  </arguments>
+                  <mainClass>${mainClass}</mainClass>
+                  <includeProjectDependencies>true</includeProjectDependencies>
+                  <includePluginDependencies>false</includePluginDependencies>
                 </configuration>
               </execution>
             </executions>
diff --git 
a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java
 
b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java
new file mode 100644
index 000000000..9f3212f87
--- /dev/null
+++ 
b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java
@@ -0,0 +1,110 @@
+/*
+ * 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.graalvm;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import org.apache.fory.Fory;
+import org.apache.fory.builder.Generated;
+import org.apache.fory.util.GraalvmSupport;
+import org.apache.fory.util.Preconditions;
+
+public class FeatureTestExample {
+
+  public static class PrivateConstructorClass {
+    private String value;
+
+    private PrivateConstructorClass() {}
+
+    public PrivateConstructorClass(String value) {
+      this.value = value;
+    }
+
+    public String getValue() {
+      return value;
+    }
+  }
+
+  public interface TestInterface {
+    String getValue();
+  }
+
+  public static class TestInvocationHandler implements InvocationHandler {
+    private final String value;
+
+    public TestInvocationHandler(String value) {
+      this.value = value;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) {
+      if ("getValue".equals(method.getName())) {
+        return value;
+      }
+      return null;
+    }
+  }
+
+  static Fory fory;
+
+  static {
+    fory = createFory();
+  }
+
+  private static Fory createFory() {
+    Fory fory =
+        Fory.builder()
+            .withName(FeatureTestExample.class.getName())
+            .requireClassRegistration(true)
+            .build();
+    fory.register(PrivateConstructorClass.class);
+    fory.register(TestInvocationHandler.class);
+    GraalvmSupport.registerProxySupport(TestInterface.class);
+    fory.ensureSerializersCompiled();
+    return fory;
+  }
+
+  public static void main(String[] args) {
+    System.out.println("Testing Fory GraalVM Feature...");
+
+    // Test class with private constructor
+    PrivateConstructorClass original = new 
PrivateConstructorClass("test-value");
+    PrivateConstructorClass deserialized =
+        (PrivateConstructorClass) fory.deserialize(fory.serialize(original));
+    Preconditions.checkArgument(
+        fory.getClassResolver().getSerializer(PrivateConstructorClass.class) 
instanceof Generated);
+    Preconditions.checkArgument("test-value".equals(deserialized.getValue()));
+    System.out.println("Private constructor class test passed");
+
+    // Test proxy serialization
+    TestInterface proxy =
+        (TestInterface)
+            Proxy.newProxyInstance(
+                TestInterface.class.getClassLoader(),
+                new Class[] {TestInterface.class},
+                new TestInvocationHandler("proxy-value"));
+    TestInterface deserializedProxy = (TestInterface) 
fory.deserialize(fory.serialize(proxy));
+    
Preconditions.checkArgument("proxy-value".equals(deserializedProxy.getValue()));
+    System.out.println("Proxy serialization test passed");
+
+    System.out.println("FeatureTestExample succeed");
+  }
+}
diff --git 
a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java
 
b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java
index d1e1591b8..74a02e5c4 100644
--- 
a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java
+++ 
b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java
@@ -40,5 +40,6 @@ public class Main {
     Benchmark.main(args);
     CollectionExample.main(args);
     ArrayExample.main(args);
+    FeatureTestExample.main(args);
   }
 }
diff --git 
a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties
 
b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties
index 60369d682..f919616b7 100644
--- 
a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties
+++ 
b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties
@@ -33,4 +33,6 @@ Args=-H:+ReportExceptionStackTraces \
   org.apache.fory.graalvm.CollectionExample,\
   org.apache.fory.graalvm.ArrayExample,\
   org.apache.fory.graalvm.Benchmark,\
-  org.apache.fory.graalvm.Main
+  org.apache.fory.graalvm.FeatureTestExample,\
+  org.apache.fory.graalvm.FeatureTestExample$PrivateConstructorClass,\
+  org.apache.fory.graalvm.FeatureTestExample$TestInvocationHandler
diff --git 
a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java
 
b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java
new file mode 100644
index 000000000..01a4047b8
--- /dev/null
+++ 
b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.util;
+
+import org.apache.fory.util.record.RecordUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class GraalvmSupportRecordTest {
+
+  public record PublicRecord(int value, String name) {}
+
+  private record PrivateRecord(int value) {}
+
+  public static class ClassWithNoArgCtor {
+    public ClassWithNoArgCtor() {}
+  }
+
+  public static class ClassWithoutNoArgCtor {
+    public ClassWithoutNoArgCtor(int value) {}
+  }
+
+  @Test
+  public void testIsRecord() {
+    Assert.assertTrue(RecordUtils.isRecord(PublicRecord.class));
+    Assert.assertTrue(RecordUtils.isRecord(PrivateRecord.class));
+    Assert.assertFalse(RecordUtils.isRecord(ClassWithNoArgCtor.class));
+    Assert.assertFalse(RecordUtils.isRecord(String.class));
+  }
+
+  @Test
+  public void testIsRecordConstructorPublicAccessible() {
+    
Assert.assertTrue(GraalvmSupport.isRecordConstructorPublicAccessible(PublicRecord.class));
+    
Assert.assertFalse(GraalvmSupport.isRecordConstructorPublicAccessible(PrivateRecord.class));
+    Assert.assertFalse(
+        
GraalvmSupport.isRecordConstructorPublicAccessible(ClassWithNoArgCtor.class));
+  }
+
+  @Test
+  public void testNeedReflectionRegisterForCreation() {
+    // Public record with public constructor doesn't need reflection 
registration
+    
Assert.assertFalse(GraalvmSupport.needReflectionRegisterForCreation(PublicRecord.class));
+    // Private record needs reflection registration
+    
Assert.assertTrue(GraalvmSupport.needReflectionRegisterForCreation(PrivateRecord.class));
+    // Class with no-arg constructor doesn't need reflection registration
+    
Assert.assertFalse(GraalvmSupport.needReflectionRegisterForCreation(ClassWithNoArgCtor.class));
+    // Class without no-arg constructor needs reflection registration
+    Assert.assertTrue(
+        
GraalvmSupport.needReflectionRegisterForCreation(ClassWithoutNoArgCtor.class));
+    // Interface doesn't need reflection registration
+    
Assert.assertFalse(GraalvmSupport.needReflectionRegisterForCreation(Runnable.class));
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java 
b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java
index 9b61dbb98..7cc0fbe36 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java
@@ -82,11 +82,11 @@ public class ObjectCreators {
       return new RecordObjectCreator<>(type);
     }
     Constructor<T> noArgConstructor = 
ReflectionUtils.getNoArgConstructor(type);
-    if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE && Platform.JAVA_VERSION >= 25) 
{
+    if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
       if (noArgConstructor != null) {
         return new DeclaredNoArgCtrObjectCreator<>(type);
       } else {
-        return new ParentNoArgCtrObjectCreator<>(type);
+        return new UnsafeObjectCreator<>(type);
       }
     }
     if (noArgConstructor == null) {
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
index e6d38b44b..52856f3e4 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
@@ -433,6 +433,7 @@ public class ClassResolver extends TypeResolver {
     registeredId2ClassInfo[id] = classInfo;
     extRegistry.registeredClasses.put(cls.getName(), cls);
     extRegistry.classIdGenerator++;
+    GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
   }
 
   public void register(String className, int classId) {
@@ -479,6 +480,7 @@ public class ClassResolver extends TypeResolver {
     compositeNameBytes2ClassInfo.put(
         new TypeNameBytes(nsBytes.hashCode, nameBytes.hashCode), classInfo);
     extRegistry.registeredClasses.put(fullname, cls);
+    GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
   }
 
   private void checkRegistration(Class<?> cls, short classId, String name) {
@@ -1802,6 +1804,7 @@ public class ClassResolver extends TypeResolver {
           fory, JdkProxySerializer.SUBT_PROXY.getClass(), 
JdkProxySerializer.class);
       classInfoMap.forEach(
           (cls, classInfo) -> {
+            GraalvmSupport.registerClass(cls, 
fory.getConfig().getConfigHash());
             if (classInfo.serializer == null) {
               if (isSerializable(classInfo.cls)) {
                 createSerializer0(cls);
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
index 9cd7bd73b..f86bb0c18 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
@@ -26,9 +26,7 @@ import com.google.common.collect.HashBiMap;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Type;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -36,7 +34,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
 import org.apache.fory.Fory;
 import org.apache.fory.annotation.Internal;
@@ -581,34 +578,18 @@ public abstract class TypeResolver {
     extRegistry.typeChecker = typeChecker;
   }
 
-  private static final ConcurrentMap<Integer, GraalvmClassRegistry> 
GRAALVM_REGISTRY =
-      new ConcurrentHashMap<>();
-
   // CHECKSTYLE.OFF:MethodName
   public static void _addGraalvmClassRegistry(int foryConfigHash, 
ClassResolver classResolver) {
     // CHECKSTYLE.ON:MethodName
     if (GraalvmSupport.isGraalBuildtime()) {
-      GraalvmClassRegistry registry =
-          GRAALVM_REGISTRY.computeIfAbsent(foryConfigHash, k -> new 
GraalvmClassRegistry());
+      GraalvmSupport.GraalvmClassRegistry registry =
+          GraalvmSupport.getClassRegistry(foryConfigHash);
       registry.resolvers.add(classResolver);
     }
   }
 
-  static class GraalvmClassRegistry {
-    final List<ClassResolver> resolvers;
-    final Map<Class<?>, Class<? extends Serializer>> serializerClassMap;
-    final Map<Long, Class<? extends Serializer>> deserializerClassMap;
-
-    private GraalvmClassRegistry() {
-      resolvers = Collections.synchronizedList(new ArrayList<>());
-      serializerClassMap = new ConcurrentHashMap<>();
-      deserializerClassMap = new ConcurrentHashMap<>();
-    }
-  }
-
-  final GraalvmClassRegistry getGraalvmClassRegistry() {
-    return GRAALVM_REGISTRY.computeIfAbsent(
-        fory.getConfig().getConfigHash(), k -> new GraalvmClassRegistry());
+  final GraalvmSupport.GraalvmClassRegistry getGraalvmClassRegistry() {
+    return GraalvmSupport.getClassRegistry(fory.getConfig().getConfigHash());
   }
 
   final Class<? extends Serializer> getGraalvmSerializerClass(Serializer 
serializer) {
@@ -619,13 +600,13 @@ public abstract class TypeResolver {
   }
 
   final Class<? extends Serializer> 
getSerializerClassFromGraalvmRegistry(Class<?> cls) {
-    GraalvmClassRegistry registry = getGraalvmClassRegistry();
-    List<ClassResolver> classResolvers = registry.resolvers;
-    if (classResolvers.isEmpty()) {
+    GraalvmSupport.GraalvmClassRegistry registry = getGraalvmClassRegistry();
+    List<TypeResolver> resolvers = registry.resolvers;
+    if (resolvers.isEmpty()) {
       return null;
     }
-    for (ClassResolver classResolver : classResolvers) {
-      if (classResolver != this) {
+    for (TypeResolver resolver : resolvers) {
+      if (resolver != this) {
         ClassInfo classInfo = getClassInfo(cls, false);
         if (classInfo != null && classInfo.serializer != null) {
           return classInfo.serializer.getClass();
@@ -648,9 +629,9 @@ public abstract class TypeResolver {
 
   private Class<? extends Serializer> 
getMetaSharedDeserializerClassFromGraalvmRegistry(
       Class<?> cls, ClassDef classDef) {
-    GraalvmClassRegistry registry = getGraalvmClassRegistry();
-    List<ClassResolver> classResolvers = registry.resolvers;
-    if (classResolvers.isEmpty()) {
+    GraalvmSupport.GraalvmClassRegistry registry = getGraalvmClassRegistry();
+    List<TypeResolver> resolvers = registry.resolvers;
+    if (resolvers.isEmpty()) {
       return null;
     }
     Class<? extends Serializer> deserializerClass =
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
index 60a6ce913..fb1b797d2 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
@@ -97,6 +97,7 @@ import org.apache.fory.type.GenericType;
 import org.apache.fory.type.Generics;
 import org.apache.fory.type.TypeUtils;
 import org.apache.fory.type.Types;
+import org.apache.fory.util.GraalvmSupport;
 import org.apache.fory.util.Preconditions;
 
 @SuppressWarnings({"unchecked", "rawtypes"})
@@ -167,6 +168,7 @@ public class XtypeResolver extends TypeResolver {
     ClassInfo classInfo = classInfoMap.get(type);
     if (type.isArray()) {
       buildClassInfo(type);
+      GraalvmSupport.registerClass(type, fory.getConfig().getConfigHash());
       return;
     }
     Serializer<?> serializer = null;
@@ -257,6 +259,7 @@ public class XtypeResolver extends TypeResolver {
     String qualifiedName = qualifiedName(namespace, typeName);
     qualifiedType2ClassInfo.put(qualifiedName, classInfo);
     extRegistry.registeredClasses.put(qualifiedName, type);
+    GraalvmSupport.registerClass(type, fory.getConfig().getConfigHash());
     if (serializer == null) {
       if (type.isEnum()) {
         classInfo.serializer = new EnumSerializer(fory, (Class<Enum>) type);
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java
index 9c2bc7418..38522d8dc 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java
@@ -28,6 +28,7 @@ import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.Platform;
 import org.apache.fory.reflect.ReflectionUtils;
 import org.apache.fory.resolver.RefResolver;
+import org.apache.fory.util.GraalvmSupport;
 import org.apache.fory.util.Preconditions;
 
 /** Serializer for jdk {@link Proxy}. */
@@ -62,7 +63,11 @@ public class JdkProxySerializer extends Serializer {
   public JdkProxySerializer(Fory fory, Class cls) {
     super(fory, cls);
     if (cls != ReplaceStub.class) {
-      Preconditions.checkArgument(ReflectionUtils.isJdkProxy(cls), "Require a 
jdk proxy class");
+      // Skip proxy class validation in GraalVM native image runtime to avoid 
issues with proxy
+      // detection
+      if (!GraalvmSupport.isGraalRuntime()) {
+        Preconditions.checkArgument(ReflectionUtils.isJdkProxy(cls), "Require 
a jdk proxy class");
+      }
     }
   }
 
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
index 48d56e11d..7a3c2ad40 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
@@ -55,6 +55,8 @@ import org.apache.fory.logging.Logger;
 import org.apache.fory.logging.LoggerFactory;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.Platform;
+import org.apache.fory.reflect.ObjectCreator;
+import org.apache.fory.reflect.ObjectCreators;
 import org.apache.fory.reflect.ReflectionUtils;
 import org.apache.fory.resolver.ClassInfo;
 import org.apache.fory.resolver.FieldResolver;
@@ -80,10 +82,60 @@ import org.apache.fory.util.unsafe._JDKAccess;
 public class ObjectStreamSerializer extends AbstractObjectSerializer {
   private static final Logger LOG = 
LoggerFactory.getLogger(ObjectStreamSerializer.class);
 
-  private final SlotsInfo[] slotsInfos;
+  private final SlotInfo[] slotsInfos;
+
+  /**
+   * Interface for slot information used in ObjectStreamSerializer. This 
allows both full SlotsInfo
+   * and minimal MinimalSlotsInfo implementations.
+   */
+  private interface SlotInfo {
+    Class<?> getCls();
+
+    StreamClassInfo getStreamClassInfo();
+
+    CompatibleSerializerBase getSlotsSerializer();
+
+    ForyObjectOutputStream getObjectOutputStream();
+
+    ForyObjectInputStream getObjectInputStream();
+
+    ObjectArray getFieldPool();
+
+    ObjectIntMap<String> getFieldIndexMap();
+
+    FieldResolver getPutFieldsResolver();
+
+    CompatibleSerializer getCompatibleStreamSerializer();
+  }
+
+  /**
+   * Safe wrapper for ObjectStreamClass.lookup that handles GraalVM native 
image limitations. In
+   * GraalVM native image, ObjectStreamClass.lookup may fail for certain 
classes like Throwable due
+   * to missing SerializationConstructorAccessor. This method catches such 
errors and returns null,
+   * allowing the serializer to use alternative approaches like 
Unsafe.allocateInstance.
+   */
+  private static ObjectStreamClass safeObjectStreamClassLookup(Class<?> type) {
+    if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+      try {
+        return ObjectStreamClass.lookup(type);
+      } catch (Throwable e) {
+        // In GraalVM native image, ObjectStreamClass.lookup may fail for 
certain classes
+        // due to missing SerializationConstructorAccessor. We catch this and 
return null
+        // to allow fallback to Unsafe-based object creation.
+        LOG.warn(
+            "ObjectStreamClass.lookup failed for {} in GraalVM native image: 
{}",
+            type.getName(),
+            e.getMessage());
+        return null;
+      }
+    } else {
+      // In regular JVM, use normal lookup
+      return ObjectStreamClass.lookup(type);
+    }
+  }
 
   public ObjectStreamSerializer(Fory fory, Class<?> type) {
-    super(fory, type);
+    super(fory, type, createObjectCreatorForGraalVM(type));
     if (!Serializable.class.isAssignableFrom(type)) {
       throw new IllegalArgumentException(
           String.format("Class %s should implement %s.", type, 
Serializable.class));
@@ -96,34 +148,62 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
         Externalizable.class.getName());
     // stream serializer may be data serializer of ReplaceResolver serializer.
     fory.getClassResolver().setSerializerIfAbsent(type, this);
-    List<SlotsInfo> slotsInfoList = new ArrayList<>();
+    List<SlotInfo> slotsInfoList = new ArrayList<>();
     Class<?> end = type;
     // locate closest non-serializable superclass
     while (end != null && Serializable.class.isAssignableFrom(end)) {
       end = end.getSuperclass();
     }
     while (type != end) {
-      slotsInfoList.add(new SlotsInfo(fory, type));
+      try {
+        slotsInfoList.add(new SlotsInfo(fory, type));
+      } catch (Exception e) {
+        if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+          LOG.warn(
+              "Failed to create SlotsInfo for {} in GraalVM native image, "
+                  + "using minimal serialization support: {}",
+              type.getName(),
+              e.getMessage());
+          // Create a minimal SlotsInfo that can work with Unsafe
+          slotsInfoList.add(new MinimalSlotsInfo(fory, type));
+        } else {
+          throw e;
+        }
+      }
       type = type.getSuperclass();
     }
     Collections.reverse(slotsInfoList);
-    slotsInfos = slotsInfoList.toArray(new SlotsInfo[0]);
+    slotsInfos = slotsInfoList.toArray(new SlotInfo[0]);
+  }
+
+  /**
+   * Creates an appropriate ObjectCreator for GraalVM native image 
environment. In GraalVM, we
+   * prefer UnsafeObjectCreator to avoid serialization constructor issues.
+   */
+  private static <T> ObjectCreator<T> createObjectCreatorForGraalVM(Class<T> 
type) {
+    if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+      // In GraalVM native image, use Unsafe to avoid serialization 
constructor issues
+      return new ObjectCreators.UnsafeObjectCreator<>(type);
+    } else {
+      // In regular JVM, use the standard object creator
+      return ObjectCreators.getObjectCreator(type);
+    }
   }
 
   @Override
   public void write(MemoryBuffer buffer, Object value) {
     buffer.writeInt16((short) slotsInfos.length);
     try {
-      for (SlotsInfo slotsInfo : slotsInfos) {
+      for (SlotInfo slotsInfo : slotsInfos) {
         // create a classinfo to avoid null class bytes when class id is a
         // replacement id.
-        classResolver.writeClassInternal(buffer, slotsInfo.classInfo.getCls());
-        StreamClassInfo streamClassInfo = slotsInfo.streamClassInfo;
+        classResolver.writeClassInternal(buffer, slotsInfo.getCls());
+        StreamClassInfo streamClassInfo = slotsInfo.getStreamClassInfo();
         Method writeObjectMethod = streamClassInfo.writeObjectMethod;
         if (writeObjectMethod == null) {
-          slotsInfo.slotsSerializer.write(buffer, value);
+          slotsInfo.getSlotsSerializer().write(buffer, value);
         } else {
-          ForyObjectOutputStream objectOutputStream = 
slotsInfo.objectOutputStream;
+          ForyObjectOutputStream objectOutputStream = 
slotsInfo.getObjectOutputStream();
           Object oldObject = objectOutputStream.targetObject;
           MemoryBuffer oldBuffer = objectOutputStream.buffer;
           ForyObjectOutputStream.PutFieldImpl oldPutField = 
objectOutputStream.curPut;
@@ -161,9 +241,9 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       TreeMap<Integer, ObjectInputValidation> callbacks = new 
TreeMap<>(Collections.reverseOrder());
       for (int i = 0; i < numClasses; i++) {
         Class<?> currentClass = classResolver.readClassInternal(buffer);
-        SlotsInfo slotsInfo = slotsInfos[slotIndex++];
-        StreamClassInfo streamClassInfo = slotsInfo.streamClassInfo;
-        while (currentClass != slotsInfo.cls) {
+        SlotInfo slotsInfo = slotsInfos[slotIndex++];
+        StreamClassInfo streamClassInfo = slotsInfo.getStreamClassInfo();
+        while (currentClass != slotsInfo.getCls()) {
           // the receiver's version extends classes that are not extended by 
the sender's version.
           Method readObjectNoData = streamClassInfo.readObjectNoData;
           if (readObjectNoData != null) {
@@ -177,14 +257,14 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
         }
         Method readObjectMethod = streamClassInfo.readObjectMethod;
         if (readObjectMethod == null) {
-          slotsInfo.slotsSerializer.readAndSetFields(buffer, obj);
+          slotsInfo.getSlotsSerializer().readAndSetFields(buffer, obj);
         } else {
-          ForyObjectInputStream objectInputStream = 
slotsInfo.objectInputStream;
+          ForyObjectInputStream objectInputStream = 
slotsInfo.getObjectInputStream();
           MemoryBuffer oldBuffer = objectInputStream.buffer;
           Object oldObject = objectInputStream.targetObject;
           ForyObjectInputStream.GetFieldImpl oldGetField = 
objectInputStream.getField;
           ForyObjectInputStream.GetFieldImpl getField =
-              (ForyObjectInputStream.GetFieldImpl) 
slotsInfo.getFieldPool.popOrNull();
+              (ForyObjectInputStream.GetFieldImpl) 
slotsInfo.getFieldPool().popOrNull();
           if (getField == null) {
             getField = new ForyObjectInputStream.GetFieldImpl(slotsInfo);
           }
@@ -205,7 +285,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
             objectInputStream.buffer = oldBuffer;
             objectInputStream.targetObject = oldObject;
             objectInputStream.getField = oldGetField;
-            slotsInfo.getFieldPool.add(getField);
+            slotsInfo.getFieldPool().add(getField);
             objectInputStream.callbacks = null;
             Arrays.fill(getField.vals, ForyObjectInputStream.NO_VALUE_STUB);
           }
@@ -239,6 +319,10 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
         e);
   }
 
+  /**
+   * Information about a class's stream methods (writeObject, readObject, 
readObjectNoData) and
+   * their optimized MethodHandle equivalents for fast invocation.
+   */
   private static class StreamClassInfo {
     private final Method writeObjectMethod;
     private final Method readObjectMethod;
@@ -249,7 +333,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
 
     private StreamClassInfo(Class<?> type) {
       // ObjectStreamClass.lookup has cache inside, invocation cost won't be 
big.
-      ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(type);
+      ObjectStreamClass objectStreamClass = safeObjectStreamClassLookup(type);
       // In JDK17, set private jdk method accessible will fail by default, use 
ObjectStreamClass
       // instead, since it set accessible.
       writeObjectMethod =
@@ -290,7 +374,12 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
         }
       };
 
-  private static class SlotsInfo {
+  /**
+   * Full implementation of SlotInfo for handling object stream serialization. 
This class manages
+   * all the details of serializing and deserializing a single class in the 
class hierarchy using
+   * Java's ObjectInputStream/ObjectOutputStream protocol.
+   */
+  private static class SlotsInfo implements SlotInfo {
     private final Class<?> cls;
     private final ClassInfo classInfo;
     private final StreamClassInfo streamClassInfo;
@@ -306,7 +395,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
     public SlotsInfo(Fory fory, Class<?> type) {
       this.cls = type;
       classInfo = fory.getClassResolver().newClassInfo(type, null, 
NO_CLASS_ID);
-      ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(type);
+      ObjectStreamClass objectStreamClass = safeObjectStreamClassLookup(type);
       streamClassInfo = STREAM_CLASS_INFO_CACHE.get(type);
       // `putFields/writeFields` will convert to fields value to be written by
       // `CompatibleSerializer`,
@@ -345,8 +434,15 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       }
       fieldIndexMap = new ObjectIntMap<>(4, 0.4f);
       List<ClassField> allFields = new ArrayList<>();
-      for (ObjectStreamField serialField : objectStreamClass.getFields()) {
-        allFields.add(new ClassField(serialField.getName(), 
serialField.getType(), cls));
+      if (objectStreamClass != null) {
+        for (ObjectStreamField serialField : objectStreamClass.getFields()) {
+          allFields.add(new ClassField(serialField.getName(), 
serialField.getType(), cls));
+        }
+      } else {
+        // Fallback to field resolver when ObjectStreamClass is not available 
in GraalVM
+        for (FieldResolver.FieldInfo fieldInfo : 
fieldResolver.getAllFieldsList()) {
+          allFields.add(new ClassField(fieldInfo.getName(), 
fieldInfo.getField().getType(), cls));
+        }
       }
       if (streamClassInfo.writeObjectMethod != null || 
streamClassInfo.readObjectMethod != null) {
         putFieldsResolver = new FieldResolver(fory, cls, true, allFields, new 
HashSet<>());
@@ -382,12 +478,141 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       getFieldPool = new ObjectArray();
     }
 
+    @Override
+    public Class<?> getCls() {
+      return cls;
+    }
+
+    @Override
+    public StreamClassInfo getStreamClassInfo() {
+      return streamClassInfo;
+    }
+
+    @Override
+    public CompatibleSerializerBase getSlotsSerializer() {
+      return slotsSerializer;
+    }
+
+    @Override
+    public ForyObjectOutputStream getObjectOutputStream() {
+      return objectOutputStream;
+    }
+
+    @Override
+    public ForyObjectInputStream getObjectInputStream() {
+      return objectInputStream;
+    }
+
+    @Override
+    public ObjectArray getFieldPool() {
+      return getFieldPool;
+    }
+
+    @Override
+    public ObjectIntMap<String> getFieldIndexMap() {
+      return fieldIndexMap;
+    }
+
+    @Override
+    public FieldResolver getPutFieldsResolver() {
+      return putFieldsResolver;
+    }
+
+    @Override
+    public CompatibleSerializer getCompatibleStreamSerializer() {
+      return compatibleStreamSerializer;
+    }
+
     @Override
     public String toString() {
       return "SlotsInfo{" + "cls=" + cls + '}';
     }
   }
 
+  /**
+   * Minimal SlotsInfo implementation for GraalVM native image when 
ObjectStreamClass.lookup fails.
+   * This provides basic serialization support using Unsafe-based object 
creation.
+   */
+  private static class MinimalSlotsInfo implements SlotInfo {
+    private final Class<?> cls;
+    private final StreamClassInfo streamClassInfo;
+    private CompatibleSerializerBase slotsSerializer;
+    private final ObjectIntMap<String> fieldIndexMap;
+    private final FieldResolver putFieldsResolver;
+    private final CompatibleSerializer compatibleStreamSerializer;
+    private final ForyObjectOutputStream objectOutputStream;
+    private final ForyObjectInputStream objectInputStream;
+    private final ObjectArray getFieldPool;
+
+    public MinimalSlotsInfo(Fory fory, Class<?> type) {
+      // Initialize with minimal required fields
+      this.cls = type;
+      this.streamClassInfo = null; // Skip problematic ObjectStreamClass lookup
+
+      // Create a basic CompatibleSerializer for field handling
+      FieldResolver fieldResolver = FieldResolver.of(fory, type, false, true);
+      this.slotsSerializer = new CompatibleSerializer(fory, type, 
fieldResolver);
+
+      // Initialize other fields with safe defaults
+      this.fieldIndexMap = new ObjectIntMap<>(4, 0.4f);
+      this.putFieldsResolver = null;
+      this.compatibleStreamSerializer = null;
+      this.objectOutputStream = null;
+      this.objectInputStream = null;
+      this.getFieldPool = new ObjectArray();
+    }
+
+    @Override
+    public Class<?> getCls() {
+      return cls;
+    }
+
+    @Override
+    public StreamClassInfo getStreamClassInfo() {
+      return streamClassInfo;
+    }
+
+    @Override
+    public CompatibleSerializerBase getSlotsSerializer() {
+      return slotsSerializer;
+    }
+
+    @Override
+    public ForyObjectOutputStream getObjectOutputStream() {
+      return objectOutputStream;
+    }
+
+    @Override
+    public ForyObjectInputStream getObjectInputStream() {
+      return objectInputStream;
+    }
+
+    @Override
+    public ObjectArray getFieldPool() {
+      return getFieldPool;
+    }
+
+    @Override
+    public ObjectIntMap<String> getFieldIndexMap() {
+      return fieldIndexMap;
+    }
+
+    @Override
+    public FieldResolver getPutFieldsResolver() {
+      return putFieldsResolver;
+    }
+
+    @Override
+    public CompatibleSerializer getCompatibleStreamSerializer() {
+      return compatibleStreamSerializer;
+    }
+
+    @Override
+    public String toString() {
+      return "MinimalSlotsInfo{" + "cls=" + cls + '}';
+    }
+  }
+
   /**
    * Implement serialization for object output with `writeObject/readObject` 
defined by java
    * serialization output spec.
@@ -398,15 +623,15 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
   private static class ForyObjectOutputStream extends ObjectOutputStream {
     private final Fory fory;
     private final boolean compressInt;
-    private final SlotsInfo slotsInfo;
+    private final SlotInfo slotsInfo;
     private MemoryBuffer buffer;
     private Object targetObject;
     private boolean fieldsWritten;
 
-    protected ForyObjectOutputStream(SlotsInfo slotsInfo) throws IOException {
+    protected ForyObjectOutputStream(SlotInfo slotsInfo) throws IOException {
       super();
       this.slotsInfo = slotsInfo;
-      this.fory = slotsInfo.slotsSerializer.fory;
+      this.fory = slotsInfo.getSlotsSerializer().fory;
       this.compressInt = fory.compressInt();
     }
 
@@ -428,21 +653,20 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
      *     
href="https://docs.oracle.com/en/java/javase/18/docs/specs/serialization/input.html#the-objectinputstream.getfield-class";>ObjectInputStream.GetField</a>
      * @see ConcurrentHashMap
      */
-    // See `defaultReadObject` in ConcurrentHashMap#readObject skip fields 
written by
-    // `writeFields()`.
     private class PutFieldImpl extends PutField {
       private final Object[] vals;
 
       PutFieldImpl() {
-        vals = new Object[slotsInfo.putFieldsResolver.getNumFields()];
+        vals = new Object[slotsInfo.getPutFieldsResolver().getNumFields()];
       }
 
       private void putValue(String name, Object val) {
-        int index = slotsInfo.fieldIndexMap.get(name, -1);
+        int index = slotsInfo.getFieldIndexMap().get(name, -1);
         if (index == -1) {
           throw new IllegalArgumentException(
               String.format(
-                  "Field name %s not exist in class %s", name, 
slotsInfo.slotsSerializer.type));
+                  "Field name %s not exist in class %s",
+                  name, slotsInfo.getSlotsSerializer().type));
         }
         vals[index] = val;
       }
@@ -495,7 +719,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       @Deprecated
       @Override
       public void write(ObjectOutput out) throws IOException {
-        Class cls = slotsInfo.slotsSerializer.type;
+        Class cls = slotsInfo.getSlotsSerializer().getType();
         throwUnsupportedEncodingException(cls);
       }
     }
@@ -524,7 +748,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       if (curPut == null) {
         throw new NotActiveException("no current PutField object");
       }
-      slotsInfo.compatibleStreamSerializer.writeFieldsValues(buffer, 
curPut.vals);
+      slotsInfo.getCompatibleStreamSerializer().writeFieldsValues(buffer, 
curPut.vals);
       Arrays.fill(curPut.vals, null);
       putFieldsCache.add(curPut);
       this.curPut = null;
@@ -536,13 +760,13 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       if (fieldsWritten) {
         throw new NotActiveException("not in writeObject invocation or fields 
already written");
       }
-      slotsInfo.slotsSerializer.write(buffer, targetObject);
+      slotsInfo.getSlotsSerializer().write(buffer, targetObject);
       fieldsWritten = true;
     }
 
     @Override
     public void reset() throws IOException {
-      Class cls = slotsInfo.slotsSerializer.getType();
+      Class cls = slotsInfo.getSlotsSerializer().getType();
       // Fory won't invoke this method, throw exception if the user invokes it.
       throwUnsupportedEncodingException(cls);
     }
@@ -659,7 +883,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
 
     @Override
     public void useProtocolVersion(int version) throws IOException {
-      Class cls = slotsInfo.cls;
+      Class cls = slotsInfo.getCls();
       throwUnsupportedEncodingException(cls);
     }
 
@@ -683,15 +907,15 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
   private static class ForyObjectInputStream extends ObjectInputStream {
     private final Fory fory;
     private final boolean compressInt;
-    private final SlotsInfo slotsInfo;
+    private final SlotInfo slotsInfo;
     private MemoryBuffer buffer;
     private Object targetObject;
     private GetFieldImpl getField;
     private boolean fieldsRead;
     private TreeMap<Integer, ObjectInputValidation> callbacks;
 
-    protected ForyObjectInputStream(SlotsInfo slotsInfo) throws IOException {
-      this.fory = slotsInfo.slotsSerializer.fory;
+    protected ForyObjectInputStream(SlotInfo slotsInfo) throws IOException {
+      this.fory = slotsInfo.getSlotsSerializer().fory;
       this.compressInt = fory.compressInt();
       this.slotsInfo = slotsInfo;
     }
@@ -708,24 +932,28 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
 
     private static final Object NO_VALUE_STUB = new Object();
 
+    /**
+     * Implementation of ObjectInputStream.GetField for reading fields that 
may not exist in the
+     * current class version.
+     */
     private static class GetFieldImpl extends GetField {
-      private final SlotsInfo slotsInfo;
+      private final SlotInfo slotsInfo;
       private final Object[] vals;
 
-      GetFieldImpl(SlotsInfo slotsInfo) {
+      GetFieldImpl(SlotInfo slotsInfo) {
         this.slotsInfo = slotsInfo;
-        vals = new Object[slotsInfo.putFieldsResolver.getNumFields()];
+        vals = new Object[slotsInfo.getPutFieldsResolver().getNumFields()];
         Arrays.fill(vals, NO_VALUE_STUB);
       }
 
       @Override
       public ObjectStreamClass getObjectStreamClass() {
-        return ObjectStreamClass.lookup(slotsInfo.cls);
+        return safeObjectStreamClassLookup(slotsInfo.getCls());
       }
 
       @Override
       public boolean defaulted(String name) throws IOException {
-        int index = slotsInfo.fieldIndexMap.get(name, -1);
+        int index = slotsInfo.getFieldIndexMap().get(name, -1);
         checkFieldExists(name, index);
         return vals[index] == NO_VALUE_STUB;
       }
@@ -812,7 +1040,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       }
 
       private Object getFieldValue(String name) {
-        int index = slotsInfo.fieldIndexMap.get(name, -1);
+        int index = slotsInfo.getFieldIndexMap().get(name, -1);
         checkFieldExists(name, index);
         return vals[index];
       }
@@ -821,7 +1049,8 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
         if (index == -1) {
           throw new IllegalArgumentException(
               String.format(
-                  "Field name %s not exist in class %s", name, 
slotsInfo.slotsSerializer.type));
+                  "Field name %s not exist in class %s",
+                  name, slotsInfo.getSlotsSerializer().getType()));
         }
       }
     }
@@ -833,7 +1062,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       if (fieldsRead) {
         throw new NotActiveException("not in readObject invocation or fields 
already read");
       }
-      slotsInfo.compatibleStreamSerializer.readFields(buffer, getField.vals);
+      slotsInfo.getCompatibleStreamSerializer().readFields(buffer, 
getField.vals);
       fieldsRead = true;
       return getField;
     }
@@ -843,7 +1072,7 @@ public class ObjectStreamSerializer extends 
AbstractObjectSerializer {
       if (fieldsRead) {
         throw new NotActiveException("not in readObject invocation or fields 
already read");
       }
-      slotsInfo.slotsSerializer.readAndSetFields(buffer, targetObject);
+      slotsInfo.getSlotsSerializer().readAndSetFields(buffer, targetObject);
       fieldsRead = true;
     }
 
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java 
b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java
index 7bfe2b00b..e46833e33 100644
--- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java
+++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java
@@ -20,11 +20,20 @@
 package org.apache.fory.util;
 
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import org.apache.fory.Fory;
 import org.apache.fory.exception.ForyException;
 import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.resolver.TypeResolver;
 import org.apache.fory.serializer.Serializer;
+import org.apache.fory.util.record.RecordUtils;
 
 /** A helper for Graalvm native image support. */
 public class GraalvmSupport {
@@ -37,6 +46,9 @@ public class GraalvmSupport {
   private static final String GRAAL_IMAGE_BUILDTIME = "buildtime";
   private static final String GRAAL_IMAGE_RUNTIME = "runtime";
 
+  private static final Map<Integer, GraalvmClassRegistry> GRAALVM_REGISTRY =
+      new ConcurrentHashMap<>();
+
   static {
     String imageCode = System.getProperty(GRAAL_IMAGE_CODE_KEY);
     IN_GRAALVM_NATIVE_IMAGE = imageCode != null;
@@ -54,6 +66,78 @@ public class GraalvmSupport {
         && 
GRAAL_IMAGE_RUNTIME.equals(System.getProperty(GRAAL_IMAGE_CODE_KEY));
   }
 
+  /** Returns all classes registered for GraalVM native image compilation. */
+  public static Set<Class<?>> getRegisteredClasses() {
+    Set<Class<?>> allClasses = ConcurrentHashMap.newKeySet();
+    for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) {
+      allClasses.addAll(registry.registeredClasses);
+    }
+    return Collections.unmodifiableSet(allClasses);
+  }
+
+  /** Returns all proxy interfaces registered for GraalVM native image 
compilation. */
+  public static Set<Class<?>> getProxyInterfaces() {
+    Set<Class<?>> allInterfaces = ConcurrentHashMap.newKeySet();
+    for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) {
+      allInterfaces.addAll(registry.proxyInterfaces);
+    }
+    return Collections.unmodifiableSet(allInterfaces);
+  }
+
+  /** Clears all GraalVM native image registrations. Primarily for testing 
purposes. */
+  public static void clearRegistrations() {
+    for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) {
+      registry.registeredClasses.clear();
+      registry.proxyInterfaces.clear();
+    }
+  }
+
+  /**
+   * Register a class in the GraalVM registry for native image compilation.
+   *
+   * @param cls the class to register
+   * @param configHash the configuration hash for the Fory instance
+   */
+  public static void registerClass(Class<?> cls, int configHash) {
+    if (!IN_GRAALVM_NATIVE_IMAGE) {
+      return;
+    }
+    GraalvmClassRegistry registry =
+        GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new 
GraalvmClassRegistry());
+    registry.registeredClasses.add(cls);
+  }
+
+  /**
+   * Register a proxy interface in the GraalVM registry for native image 
compilation.
+   *
+   * @param proxyInterface the proxy interface to register
+   * @param configHash the configuration hash for the Fory instance
+   */
+  public static void registerProxyInterface(Class<?> proxyInterface, int 
configHash) {
+    if (!IN_GRAALVM_NATIVE_IMAGE) {
+      return;
+    }
+    if (proxyInterface == null) {
+      throw new NullPointerException("Proxy interface must not be null");
+    }
+    if (!proxyInterface.isInterface()) {
+      throw new IllegalArgumentException(
+          "Proxy type must be an interface: " + proxyInterface.getName());
+    }
+    GraalvmClassRegistry registry =
+        GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new 
GraalvmClassRegistry());
+    registry.proxyInterfaces.add(proxyInterface);
+  }
+
+  /**
+   * Register proxy support for GraalVM native image compilation.
+   *
+   * @param proxyInterface the proxy interface to register
+   */
+  public static void registerProxySupport(Class<?> proxyInterface) {
+    registerProxyInterface(proxyInterface, 0);
+  }
+
   public static class GraalvmSerializerHolder extends Serializer {
     private final Class serializerClass;
     private Serializer serializer;
@@ -96,4 +180,93 @@ public class GraalvmSupport {
   public static ForyException throwNoArgCtrException(Class<?> type) {
     throw new ForyException("Please provide a no-arg constructor for " + type);
   }
+
+  public static boolean isRecordConstructorPublicAccessible(Class<?> type) {
+    if (!RecordUtils.isRecord(type)) {
+      return false;
+    }
+
+    try {
+      Constructor<?>[] constructors = type.getDeclaredConstructors();
+      for (Constructor<?> constructor : constructors) {
+        if (Modifier.isPublic(constructor.getModifiers())) {
+          Class<?>[] paramTypes = constructor.getParameterTypes();
+          boolean allParamsPublic = true;
+          for (Class<?> paramType : paramTypes) {
+            if (!Modifier.isPublic(paramType.getModifiers())) {
+              allParamsPublic = false;
+              break;
+            }
+          }
+          if (allParamsPublic) {
+            return true;
+          }
+        }
+      }
+    } catch (Exception e) {
+      return false;
+    }
+    return false;
+  }
+
+  /**
+   * Checks whether a class requires reflective instantiation handling in 
GraalVM.
+   *
+   * <p>Returns true when the class does not expose an accessible no-arg 
constructor and therefore
+   * needs reflective registration for instantiation during native image 
builds.
+   *
+   * @param type the class to check
+   * @return true if reflective instantiation handling is required, false 
otherwise
+   */
+  public static boolean needReflectionRegisterForCreation(Class<?> type) {
+    if (type.isInterface()
+        || Modifier.isAbstract(type.getModifiers())
+        || type.isArray()
+        || type.isEnum()
+        || type.isAnonymousClass()
+        || type.isLocalClass()) {
+      return false;
+    }
+    Constructor<?>[] constructors = type.getDeclaredConstructors();
+    if (constructors.length == 0) {
+      return true;
+    }
+    for (Constructor<?> constructor : constructors) {
+      if (constructor.getParameterCount() == 0) {
+        return false;
+      }
+    }
+    if (RecordUtils.isRecord(type)) {
+      return !isRecordConstructorPublicAccessible(type);
+    }
+    return true;
+  }
+
+  /**
+   * Get the GraalVM class registry for a specific configuration hash. 
Package-private method for
+   * use by TypeResolver and ClassResolver.
+   */
+  public static GraalvmClassRegistry getClassRegistry(int configHash) {
+    if (!IN_GRAALVM_NATIVE_IMAGE) {
+      return new GraalvmClassRegistry();
+    }
+    return GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new 
GraalvmClassRegistry());
+  }
+
+  /** GraalVM class registry. */
+  public static class GraalvmClassRegistry {
+    public final List<TypeResolver> resolvers;
+    public final Map<Class<?>, Class<? extends Serializer>> serializerClassMap;
+    public final Map<Long, Class<? extends Serializer>> deserializerClassMap;
+    public final Set<Class<?>> registeredClasses;
+    public final Set<Class<?>> proxyInterfaces;
+
+    private GraalvmClassRegistry() {
+      resolvers = Collections.synchronizedList(new ArrayList<>());
+      serializerClassMap = new ConcurrentHashMap<>();
+      deserializerClassMap = new ConcurrentHashMap<>();
+      registeredClasses = ConcurrentHashMap.newKeySet();
+      proxyInterfaces = ConcurrentHashMap.newKeySet();
+    }
+  }
 }
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java 
b/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java
index 0fd3f7b87..386d26fc1 100644
--- a/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java
+++ b/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java
@@ -34,6 +34,8 @@ import org.apache.fory.collection.Tuple2;
 import org.apache.fory.reflect.ReflectionUtils;
 import org.apache.fory.util.GraalvmSupport;
 import org.apache.fory.util.Preconditions;
+import org.apache.fory.util.record.RecordComponent;
+import org.apache.fory.util.record.RecordUtils;
 import org.apache.fory.util.unsafe._JDKAccess;
 
 /** Utility for lambda functions. */
@@ -71,8 +73,43 @@ public class Functions {
 
   public static Object makeGetterFunction(Class<?> cls, String methodName) {
     try {
-      return makeGetterFunction(cls.getDeclaredMethod(methodName));
+      Method method = cls.getDeclaredMethod(methodName);
+      return makeGetterFunction(method);
     } catch (NoSuchMethodException e) {
+      if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+        // In GraalVM native image, getDeclaredMethod may fail for Record 
accessor methods
+        // For Record classes, use RecordUtils which uses 
getRecordComponents() API
+        if (RecordUtils.isRecord(cls)) {
+          RecordComponent[] components = RecordUtils.getRecordComponents(cls);
+          if (components != null) {
+            for (RecordComponent component : components) {
+              if (component.getName().equals(methodName)) {
+                return component.getGetter();
+              }
+            }
+          }
+        }
+        // Fall back to getDeclaredMethods() for private inner classes
+        // Then try getMethods() for public methods
+        try {
+          for (Method method : cls.getDeclaredMethods()) {
+            if (method.getName().equals(methodName) && 
method.getParameterCount() == 0) {
+              method.setAccessible(true);
+              return makeGetterFunction(method);
+            }
+          }
+          for (Method method : cls.getMethods()) {
+            if (method.getName().equals(methodName) && 
method.getParameterCount() == 0) {
+              return makeGetterFunction(method);
+            }
+          }
+          throw new NoSuchMethodException(
+              "No no-arg method found: " + cls.getName() + "." + methodName + 
"()");
+        } catch (NoSuchMethodException ex) {
+          throw new RuntimeException(
+              "Failed to create getter for " + cls.getName() + "." + 
methodName, ex);
+        }
+      }
       throw new RuntimeException(e);
     }
   }
diff --git 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
index 6994105a2..fabf1ddd1 100644
--- 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
+++ 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
@@ -490,7 +490,10 @@ 
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
     org.apache.fory.util.ClassLoaderUtils,\
     org.apache.fory.util.DelayedRef,\
     org.apache.fory.util.function.Functions,\
+    org.apache.fory.graalvm.FeatureTestExample,\
+    org.apache.fory.graalvm.Main,\
     org.apache.fory.util.GraalvmSupport,\
+    org.apache.fory.util.GraalvmSupport$GraalvmClassRegistry,\
     org.apache.fory.util.GraalvmSupport$GraalvmSerializerHolder,\
     org.apache.fory.util.LoaderBinding$1,\
     org.apache.fory.util.LoaderBinding$StagingType,\
diff --git 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json
 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json
index 154ba0da2..6eb32cd7d 100644
--- 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json
+++ 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/serialization-config.json
@@ -7,5 +7,14 @@
       { "name": "writeReplace" }
     ],
     "customTargetConstructorClass": "java.lang.Object"
-  }
+  },
+  { "name": "java.lang.Throwable" },
+  { "name": "java.lang.Exception" },
+  { "name": "java.lang.RuntimeException" },
+  { "name": "java.lang.IllegalArgumentException" },
+  { "name": "java.lang.IllegalStateException" },
+  { "name": "java.lang.NullPointerException" },
+  { "name": "java.lang.IndexOutOfBoundsException" },
+  { "name": "java.lang.ArrayIndexOutOfBoundsException" },
+  { "name": "java.io.IOException" }
 ]
diff --git a/java/fory-graalvm-feature/pom.xml 
b/java/fory-graalvm-feature/pom.xml
new file mode 100644
index 000000000..c6c7b927c
--- /dev/null
+++ b/java/fory-graalvm-feature/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <parent>
+    <artifactId>fory-parent</artifactId>
+    <groupId>org.apache.fory</groupId>
+    <version>0.14.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>fory-graalvm-feature</artifactId>
+  <name>Fory GraalVM Feature</name>
+
+  <properties>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
+    <fory.java.rootdir>${basedir}/..</fory.java.rootdir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.fory</groupId>
+      <artifactId>fory-core</artifactId>
+      <version>${project.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.graalvm.sdk</groupId>
+      <artifactId>graal-sdk</artifactId>
+      <version>23.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.testng</groupId>
+      <artifactId>testng</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestEntries>
+              
<Automatic-Module-Name>org.apache.fory.graalvm.feature</Automatic-Module-Name>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <forceJavacCompilerUse>true</forceJavacCompilerUse>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <failIfNoTests>false</failIfNoTests>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git 
a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java
 
b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java
new file mode 100644
index 000000000..3d7f8f7c2
--- /dev/null
+++ 
b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java
@@ -0,0 +1,115 @@
+/*
+ * 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.graalvm.feature;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.fory.util.GraalvmSupport;
+import org.apache.fory.util.record.RecordUtils;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.nativeimage.hosted.RuntimeReflection;
+
+/**
+ * GraalVM native image feature for Apache Fory serialization framework.
+ *
+ * <p>This feature automatically registers reflection metadata during native 
image build to ensure
+ * Fory serialization works correctly at runtime. It handles:
+ *
+ * <ul>
+ *   <li>Classes requiring reflective instantiation (private constructors, 
Records, etc.)
+ *   <li>Record class accessor methods and canonical constructors
+ *   <li>Proxy interfaces for dynamic proxy serialization
+ * </ul>
+ *
+ * <p>Usage: Add to native-image build via 
META-INF/native-image/.../native-image.properties:
+ *
+ * <pre>Args = 
--features=org.apache.fory.graalvm.feature.ForyGraalVMFeature</pre>
+ */
+public class ForyGraalVMFeature implements Feature {
+
+  private final Set<Class<?>> processedClasses = ConcurrentHashMap.newKeySet();
+  private final Set<Class<?>> processedProxyInterfaces = 
ConcurrentHashMap.newKeySet();
+
+  @Override
+  public String getDescription() {
+    return "Registers Fory serialization classes and proxy interfaces for 
GraalVM native image";
+  }
+
+  @Override
+  public void duringAnalysis(DuringAnalysisAccess access) {
+    boolean changed = false;
+
+    for (Class<?> clazz : GraalvmSupport.getRegisteredClasses()) {
+      if (processedClasses.add(clazz)) {
+        registerClass(clazz);
+        changed = true;
+      }
+    }
+
+    for (Class<?> proxyInterface : GraalvmSupport.getProxyInterfaces()) {
+      if (processedProxyInterfaces.add(proxyInterface)) {
+        RuntimeReflection.register(proxyInterface);
+        RuntimeReflection.register(proxyInterface.getMethods());
+        changed = true;
+      }
+    }
+
+    if (changed) {
+      access.requireAnalysisIteration();
+    }
+  }
+
+  private void registerClass(Class<?> clazz) {
+    RuntimeReflection.register(clazz);
+    RuntimeReflection.registerClassLookup(clazz.getName());
+
+    if (RecordUtils.isRecord(clazz)) {
+      registerRecordClass(clazz);
+    } else if (GraalvmSupport.needReflectionRegisterForCreation(clazz)) {
+      registerForReflectiveInstantiation(clazz);
+    }
+  }
+
+  private void registerRecordClass(Class<?> clazz) {
+    RuntimeReflection.registerForReflectiveInstantiation(clazz);
+    for (Field field : clazz.getDeclaredFields()) {
+      RuntimeReflection.register(field);
+      RuntimeReflection.registerFieldLookup(clazz, field.getName());
+    }
+    for (Method method : clazz.getDeclaredMethods()) {
+      RuntimeReflection.register(method);
+      RuntimeReflection.registerMethodLookup(clazz, method.getName(), 
method.getParameterTypes());
+    }
+    for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
+      RuntimeReflection.register(constructor);
+      RuntimeReflection.registerConstructorLookup(clazz, 
constructor.getParameterTypes());
+    }
+  }
+
+  private void registerForReflectiveInstantiation(Class<?> clazz) {
+    RuntimeReflection.registerForReflectiveInstantiation(clazz);
+    for (Field field : clazz.getDeclaredFields()) {
+      RuntimeReflection.register(field);
+    }
+  }
+}
diff --git 
a/java/fory-graalvm-feature/src/main/resources/META-INF/services/org.graalvm.nativeimage.hosted.Feature
 
b/java/fory-graalvm-feature/src/main/resources/META-INF/services/org.graalvm.nativeimage.hosted.Feature
new file mode 100644
index 000000000..72cf9f839
--- /dev/null
+++ 
b/java/fory-graalvm-feature/src/main/resources/META-INF/services/org.graalvm.nativeimage.hosted.Feature
@@ -0,0 +1 @@
+org.apache.fory.graalvm.feature.ForyGraalVMFeature
\ No newline at end of file
diff --git 
a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java
 
b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java
new file mode 100644
index 000000000..8dfc93e1b
--- /dev/null
+++ 
b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.graalvm.feature;
+
+import static org.testng.Assert.*;
+
+import org.apache.fory.util.GraalvmSupport;
+import org.testng.annotations.Test;
+
+public class ForyGraalVMFeatureTest {
+
+  public static class PublicNoArgConstructorClass {
+    public PublicNoArgConstructorClass() {}
+  }
+
+  public static class PrivateNoArgConstructorClass {
+    private PrivateNoArgConstructorClass() {}
+  }
+
+  public static class NoNoArgConstructorClass {
+    public NoNoArgConstructorClass(String data) {}
+  }
+
+  public abstract static class AbstractClass {}
+
+  public interface SampleInterface {}
+
+  public enum SampleEnum {
+    VALUE
+  }
+
+  @Test
+  public void testGetDescription() {
+    ForyGraalVMFeature feature = new ForyGraalVMFeature();
+    assertNotNull(feature.getDescription());
+    assertTrue(feature.getDescription().contains("Fory"));
+  }
+
+  @Test
+  public void testNeedReflectionRegisterForCreation() {
+    // Classes with public no-arg constructor don't need reflection 
registration
+    assertFalse(
+        
GraalvmSupport.needReflectionRegisterForCreation(PublicNoArgConstructorClass.class));
+
+    // Classes with private no-arg constructor don't need reflection 
registration
+    // (they have a no-arg constructor, just private)
+    assertFalse(
+        
GraalvmSupport.needReflectionRegisterForCreation(PrivateNoArgConstructorClass.class));
+
+    // Classes without no-arg constructor need reflection registration
+    
assertTrue(GraalvmSupport.needReflectionRegisterForCreation(NoNoArgConstructorClass.class));
+
+    // Abstract classes, interfaces, enums don't need reflection registration
+    
assertFalse(GraalvmSupport.needReflectionRegisterForCreation(AbstractClass.class));
+    
assertFalse(GraalvmSupport.needReflectionRegisterForCreation(SampleInterface.class));
+    
assertFalse(GraalvmSupport.needReflectionRegisterForCreation(SampleEnum.class));
+
+    // Arrays don't need reflection registration
+    
assertFalse(GraalvmSupport.needReflectionRegisterForCreation(String[].class));
+  }
+}
diff --git a/java/pom.xml b/java/pom.xml
index 0f65b78f9..cd449cb7d 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -67,6 +67,8 @@
   <properties>
     <maven.compiler.source>1.8</maven.compiler.source>
     <maven.compiler.target>1.8</maven.compiler.target>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <guava.version>32.1.2-jre</guava.version>
     <janino.version>3.1.12</janino.version>
     <commons_codec.version>1.13</commons_codec.version>
@@ -93,6 +95,7 @@
       </activation>
       <modules>
         <module>fory-simd</module>
+        <module>fory-graalvm-feature</module>
       </modules>
     </profile>
   </profiles>
@@ -177,26 +180,6 @@
             </execution>
           </executions>
         </plugin>
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-javadoc-plugin</artifactId>
-          <version>3.1.0</version>
-          <executions>
-            <execution>
-              <id>attach-javadocs</id>
-              <goals>
-                <goal>jar</goal>
-              </goals>
-            </execution>
-          </executions>
-          <configuration>
-            <doclint>none</doclint>
-            <bottom>
-              Copyright © 2023-2025, The Apache Software Foundation. Apache 
Fory™, Fory™, and Apache
-              are either registered trademarks or trademarks of the Apache 
Software Foundation.
-            </bottom>
-          </configuration>
-        </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-shade-plugin</artifactId>
@@ -284,6 +267,32 @@
           </java>
         </configuration>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>3.6.3</version>
+        <executions>
+          <execution>
+            <id>attach-javadocs</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <doclint>none</doclint>
+          <encoding>UTF-8</encoding>
+          <docencoding>UTF-8</docencoding>
+          <charset>UTF-8</charset>
+          <detectJavaApiLink>false</detectJavaApiLink>
+          <detectLinks>false</detectLinks>
+          <detectOfflineLinks>false</detectOfflineLinks>
+          <bottom>
+            Copyright © 2023-2025, The Apache Software Foundation. Apache 
Fory™, Fory™, and Apache
+            are either registered trademarks or trademarks of the Apache 
Software Foundation.
+          </bottom>
+        </configuration>
+      </plugin>
     </plugins>
   </build>
 </project>


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


Reply via email to