blob: 7dd6fe58dc66949d51e38dfc579d39f00aa9ce0a [file] [view]
---
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](graalvm-support.md).
Use [Xlang Serialization](xlang-serialization.md), the default Java mode, when bytes must be read by
non-Java Fory runtimes.
## When To Use Native Serialization
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`, or `Externalizable`.
- 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, Scala, Kotlin, or another
non-Java runtime.
## Create a Native Runtime
```java
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.
```java
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.
## Schema Evolution
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:
```java
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](schema-evolution.md)
for field IDs, class version checks, meta sharing, and unknown-class handling.
## Registration And Security
Class registration is enabled by default. Keep it enabled for service boundaries and register
application classes explicitly:
```java
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:
```java
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:
```java
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](type-registration.md) for the full security configuration.
## Java Object Surface
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(...)` or `registerSerializerAndType(...)`.
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.
## JDK Serialization Hooks
Java native mode supports the JDK serialization hooks that are part of many existing Java object
models:
- `writeObject` and `readObject`
- `writeReplace` and `readResolve`
- `readObjectNoData`
- `Externalizable`
```java
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.
## Migrating From Java Serialization Frameworks
When replacing JDK serialization, Kryo, FST, Hessian, or a Java-only Protocol Buffers pipeline:
1. Start with `.withXlang(false)` because the data is Java-only.
2. Keep `requireClassRegistration(true)` and register application classes with explicit IDs.
3. Use `.withCompatible(true)` if writer and reader deployments roll independently.
4. Enable `.withRefTracking(true)` only when identity or circular references matter.
5. Add custom serializers for hot classes that would otherwise use expensive JDK serialization hooks.
6. 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:
```java
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.
## Object Graphs And Reference Tracking
Native mode supports shared references and circular references when reference tracking is enabled:
```java
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:
```java
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.
## Object Copy
Fory can deep-copy Java objects without materializing a byte array. For full copy semantics, custom
copy hooks, and troubleshooting, see [Object Copy](object-copy.md).
```java
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.
## Zero-Copy Serialization
Native mode supports out-of-band `BufferObject` payloads for large binary values and primitive
arrays:
```java
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.
## Class Loaders
```java
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.
## Performance Guidelines
- Reuse `Fory` or `ThreadSafeFory` instances 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:
```java
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.
## Native And Xlang Comparison
| 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 |
## Troubleshooting
### A non-Java runtime cannot read the payload
The writer is using native serialization. Rebuild the writer with `.withXlang(true)` and align type
registration with every peer runtime.
### A class is rejected during deserialization
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`.
### A rolling deployment fails after a field change
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.
### Object identity is not preserved
Enable `.withRefTracking(true)` for serialization and deserialization. For `Fory.copy(...)`, enable
`.withRefCopy(true)`.
### A migrated boundary receives both JDK and Fory bytes
Use `JavaSerializer.serializedByJDK(...)` only at the mixed-format boundary, then route JDK bytes to
`ObjectInputStream` and Fory native bytes to `fory.deserialize(...)`.
## Related Topics
- [Basic Serialization](basic-serialization.md) - Xlang-first Java quickstart
- [Xlang Serialization](xlang-serialization.md) - Cross-runtime Java payloads
- [Configuration](configuration.md) - Java builder options
- [Schema Evolution](schema-evolution.md) - Compatible and schema-consistent mode
- [Type Registration](type-registration.md) - Registration and security
- [Object Copy](object-copy.md) - Deep-copy semantics
- [Custom Serializers](custom-serializers.md) - Custom Java serializers
- [Static Generated Serializers](static-generated-serializers.md) - Build-time generated serializers
- [GraalVM Support](graalvm-support.md) - Native-image platform support