| title | Native Serialization |
|---|---|
| sidebar_position | 3 |
| id | native_serialization |
| license | 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. |
Java native serialization is the Java-only wire format selected with withXlang(false). Use it
when every writer and reader is a Java/JVM process and the payload should follow the JVM type system
instead of the portable xlang type system. Native serialization is the right starting point for
Java/JVM-only replacements of JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers
payloads.
Native serialization in this page means Fory's xlang=false wire mode. It is separate from GraalVM
native image support, which is covered in GraalVM Support.
Use Xlang Serialization, the default Java mode, when bytes must be read by non-Java Fory runtimes.
Use native serialization when:
- A payload is produced and consumed only by Java/JVM applications.
- The object model uses Java-specific types, JDK collections, wrapper types, inheritance, interfaces, or polymorphism that do not need a cross-language schema.
- Existing classes rely on JDK serialization hooks such as
writeObject,readObject,writeReplace,readResolve,readObjectNoData, orExternalizable. - You need Java object copy through
Fory.copy(...). - Large primitive arrays or binary payloads should use native-mode out-of-band buffers.
- You are replacing Java-only serialization frameworks and want the broadest Java object surface.
Use xlang serialization instead when the payload must be read by Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, or another non-Java runtime.
import org.apache.fory.Fory;
Fory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(true)
.withRefTracking(true)
.build();
byte[] bytes = fory.serialize(object);
Object decoded = fory.deserialize(bytes);Create and reuse a Fory or ThreadSafeFory instance for each configuration. Fory creation is not
cheap because the runtime caches class metadata, serializers, and generated code.
import org.apache.fory.Fory;
import org.apache.fory.ThreadSafeFory;
ThreadSafeFory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(true)
.withRefTracking(true)
.buildThreadSafeFory();
fory.register(Order.class, 100);Register classes and serializers during startup before concurrent serialization starts. Use a separate runtime when class loader, registration, security, schema evolution, or reference-tracking settings differ.
Native serialization defaults to schema-consistent mode. In schema-consistent mode, writer and reader classes are expected to have the same schema. This is the most direct native-mode path and is the right default for lockstep deployments.
Enable compatible mode when Java classes can evolve independently across writer and reader deployments:
Fory fory = Fory.builder()
.withXlang(false)
.withCompatible(true)
.build();Compatible mode lets readers tolerate added, removed, or reordered fields when the schema metadata remains compatible. It also enables metadata sharing by default. See Schema Evolution for field IDs, class version checks, meta sharing, and unknown-class handling.
Class registration is enabled by default. Keep it enabled for service boundaries and register application classes explicitly:
Fory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(true)
.build();
fory.register(Order.class, 100);
fory.register(LineItem.class, 101);Explicit numeric IDs avoid registration-order drift. If you use fory.register(MyClass.class)
without an ID, every writer and reader must register classes in the same order. Name-based
registration is also available when type ID coordination is harder:
fory.register(Order.class, "com.example", "Order");Disable class registration only in trusted environments. If you need dynamic class loading, install
a TypeChecker or AllowListChecker so deserialization can reject unexpected classes:
import org.apache.fory.Fory;
import org.apache.fory.resolver.AllowListChecker;
AllowListChecker checker = new AllowListChecker(AllowListChecker.CheckLevel.STRICT);
checker.allowClass("com.example.*");
Fory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(false)
.withTypeChecker(checker)
.withMaxDepth(100)
.build();Use withMaxDepth(...) to cap object graph depth for untrusted or externally supplied payloads.
See Type Registration for the full security configuration.
Native serialization owns the Java-specific object surface:
- POJOs, records, enums, primitive arrays, object arrays, and common JDK collections.
- Inheritance, interfaces, polymorphic fields, shared references, and circular object graphs.
- Java wrapper and collection behavior that does not have to map to a portable xlang type.
- JDK serialization hooks for classes that require Java serialization compatibility.
- Custom serializers registered with
registerSerializer(...)orregisterSerializerAndType(...).
For ordinary application classes, Fory can use generated serializers and avoid JDK
ObjectOutputStream semantics. Classes that require JDK serialization hooks may use the Java
serialization-compatible path; prefer a Fory custom serializer for hot classes when the hook-based
path is too expensive.
Java native mode supports the JDK serialization hooks that are part of many existing Java object models:
writeObjectandreadObjectwriteReplaceandreadResolvereadObjectNoDataExternalizable
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyClass implements Serializable {
private void writeObject(ObjectOutputStream out) throws IOException {
// Custom serialization logic.
}
private void readObject(ObjectInputStream in) throws IOException {
// Custom deserialization logic.
}
private Object writeReplace() {
return this;
}
private Object readResolve() {
return this;
}
}Fory native payloads are not JDK ObjectOutputStream payloads. The hooks are honored for
Java-object compatibility, but new payloads should be written and read by Fory.
When replacing JDK serialization, Kryo, FST, Hessian, or a Java-only Protocol Buffers pipeline:
- Start with
.withXlang(false)because the data is Java-only. - Keep
requireClassRegistration(true)and register application classes with explicit IDs. - Use
.withCompatible(true)if writer and reader deployments roll independently. - Enable
.withRefTracking(true)only when identity or circular references matter. - Add custom serializers for hot classes that would otherwise use expensive JDK serialization hooks.
- Keep old and new byte streams separated when possible.
When an application must read data that may be either JDK ObjectOutputStream bytes or Fory
native-mode bytes, JavaSerializer.serializedByJDK can identify the JDK payload before falling
back to Fory:
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import org.apache.fory.serializer.JavaSerializer;
if (JavaSerializer.serializedByJDK(bytes)) {
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
return objectInputStream.readObject();
}
return fory.deserialize(bytes);Use this bridge only at boundaries that actually accept both formats. Native-mode Fory payloads should otherwise be written and read by Fory directly.
Native mode supports shared references and circular references when reference tracking is enabled:
Fory fory = Fory.builder()
.withXlang(false)
.withRefTracking(true)
.build();Disable reference tracking only for value-shaped graphs where identity and cycles are not part of the data model:
Fory fory = Fory.builder()
.withXlang(false)
.withRefTracking(false)
.build();Reference tracking is a semantic choice. Turning it off can improve performance and reduce payload size, but repeated references deserialize as distinct objects and cycles are unsupported.
Fory can deep-copy Java objects without materializing a byte array. For full copy semantics, custom copy hooks, and troubleshooting, see Object Copy.
Fory fory = Fory.builder()
.withXlang(false)
.withRefCopy(true)
.build();
MyClass copy = fory.copy(original);withRefCopy(true) controls reference preservation for copy operations. It is separate from
withRefTracking(...), which controls serialization and deserialization.
Native mode supports out-of-band BufferObject payloads for large binary values and primitive
arrays:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.fory.Fory;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.serializer.BufferObject;
Fory fory = Fory.builder()
.withXlang(false)
.build();
List<Object> value = Arrays.asList("str", new byte[1000], new int[100], new double[100]);
Collection<BufferObject> bufferObjects = new ArrayList<>();
byte[] bytes = fory.serialize(value, bufferObject -> !bufferObjects.add(bufferObject));
List<MemoryBuffer> buffers = bufferObjects.stream()
.map(BufferObject::toBuffer)
.collect(Collectors.toList());
Object decoded = fory.deserialize(bytes, buffers);The callback returns false for buffers that should be sent out-of-band. The main byte array still
contains the root object graph and references the buffers in callback order.
Use this when the transport can carry the main payload and buffers separately. If the stream is stored or sent as one byte array, omit the callback and let Fory keep buffer contents in-band.
Native serialization also supports byte arrays, MemoryBuffer, ByteBuffer, OutputStream,
ForyInputStream, and ForyReadableChannel APIs. Choose the API that matches the boundary you
already own; avoid copying through byte[] when a buffer or stream is already available.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Fory fory = Fory.builder()
.withXlang(false)
.withClassLoader(loader)
.build();Each Fory instance is tied to one class loader because class metadata and serializers are cached.
Build a separate runtime for each application, plugin, or tenant class loader instead of switching
loaders on an existing runtime.
-
Reuse
ForyorThreadSafeForyinstances instead of rebuilding them per request. -
Register classes with explicit numeric IDs for compact type metadata and stable deployments.
-
Keep schema-consistent mode for lockstep Java services; enable compatible mode only when schema evolution requires it.
-
Disable reference tracking for value-shaped graphs with no identity or cycles.
-
Use async compilation on ordinary JVMs when startup latency can tolerate interpreter-first serialization:
Fory fory = Fory.builder() .withXlang(false) .withAsyncCompilation(true) .build();
-
Keep runtime code generation enabled on ordinary JVMs. Use static generated serializers for GraalVM native image and Android flows.
-
Use zero-copy out-of-band buffers for large primitive arrays or binary fields when the transport supports split payloads.
-
Replace expensive JDK serialization hooks with Fory custom serializers for hot classes when the object contract allows it.
| Requirement | Use native serialization | Use xlang serialization |
|---|---|---|
| Java/JVM-only payloads | Yes | Optional |
| Non-Java readers or writers | No | Yes |
| Broad Java object surface | Yes | Limited to xlang types |
| JDK serialization hooks | Yes | No |
| Java object copy | Yes | No |
| Portable type mapping across runtimes | No | Yes |
| Compatible schema evolution by default | No | Yes |
| Schema-consistent same-language performance | Yes | No |
The writer is using native serialization. Rebuild the writer with .withXlang(true) and align type
registration with every peer runtime.
Keep class registration enabled and register the class on both writer and reader. If dynamic class
loading is intentional, use requireClassRegistration(false) only with an allow-listing
TypeChecker.
Native serialization defaults to schema-consistent mode. Use .withCompatible(true) when writer and
reader versions can differ, and add stable field metadata for long-lived schemas.
Enable .withRefTracking(true) for serialization and deserialization. For Fory.copy(...), enable
.withRefCopy(true).
Use JavaSerializer.serializedByJDK(...) only at the mixed-format boundary, then route JDK bytes to
ObjectInputStream and Fory native bytes to fory.deserialize(...).
- Basic Serialization - Xlang-first Java quickstart
- Xlang Serialization - Cross-runtime Java payloads
- Configuration - Java builder options
- Schema Evolution - Compatible and schema-consistent mode
- Type Registration - Registration and security
- Object Copy - Deep-copy semantics
- Custom Serializers - Custom Java serializers
- Static Generated Serializers - Build-time generated serializers
- GraalVM Support - Native-image platform support