Java Serialization Guide _ Apache Fory
Java Serialization Guide _ Apache Fory
Version: 0.11
Quick Start
Note that fory creation is not cheap, the fory instances should be reused between
serializations instead of creating it everytime. You should keep fory to a static global
variable, or instance variable of some singleton object or limited objects.
import java.util.List;
import java.util.Arrays;
import org.apache.fory.*;
import org.apache.fory.config.*;
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 1/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
import java.util.List;
import java.util.Arrays;
import org.apache.fory.*;
import org.apache.fory.config.*;
import java.util.List;
import java.util.Arrays;
import org.apache.fory.*;
import org.apache.fory.config.*;
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 2/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
ForyBuilder options
Option Name Description
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 3/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 4/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
Advanced Usage
Fory creation
Single thread fory:
Thread-safe fory:
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 6/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
This compatible mode involves serializing class metadata into the serialized output. Despite
Fory's use of sophisticated compression techniques to minimize overhead, there is still
some additional space cost associated with class metadata.
To further reduce metadata costs, Fory introduces a class metadata sharing mechanism,
which allows the metadata to be sent to the deserialization process only once. For more
details, please refer to the Meta Sharing specification.
Compression
ForyBuilder#withIntCompressed / ForyBuilder#withLongCompressed can be used to
compress int/long for smaller size. Normally compress int is enough.
Both compression are enabled by default, if the serialized is not important, for example, you
use flatbuffers for serialization before, which doesn't compress anything, then you should
disable compression. If your data are all numbers, the compression may bring 80%
performance regression.
For int compression, fory use 1~5 bytes for encoding. First bit in every byte indicate whether
has next byte. if first bit is set, then next byte will be read util first bit of next byte is unset.
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 7/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
If a number are long type, it can't be represented by smaller bytes mostly, the
compression won't get good enough result, not worthy compared to performance cost.
Maybe you should try to disable long compression if you find it didn't bring much space
savings.
Make fory deep copy ignore circular and shared reference, this deep copy mode will ignore
circular and shared reference. Same reference of an object graph will be copied into
different objects in one Fory#copy .
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 8/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
class Foo {
public long f1;
@Override
public void write(MemoryBuffer buffer, Foo value) {
buffer.writeInt64(value.f1);
}
@Override
public Foo read(MemoryBuffer buffer) {
Foo foo = new Foo();
foo.f1 = buffer.readInt64();
return foo;
}
}
Register serializer:
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 9/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
When true :
Enables optimized access to collection elements and JIT compilation for better
performance
Direct serialization invocation and inline for map key-value items without dynamic
serializer dispatch cost.
Better performance for standard collection types
Recommended for most collections
When false :
When implementing a Collection serializer with JIT support, you can leverage Fory's
existing binary format and collection serialization infrastructure. The key is to properly
implement the onCollectionWrite and newCollection methods to handle metadata
while letting Fory handle the element serialization.
Here's an example:
@Override
public Collection onCollectionWrite(MemoryBuffer buffer, T value)
{
// Write collection size
buffer.writeVarUint32Small7(value.size());
// Write any additional collection metadata
return value;
}
@Override
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 10/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
Note that please invoke setNumElements when implementing newCollection to let fory
know how many elements to deserialize.
Sometimes you need to serialize a collection type that uses primitive arrays or has special
requirements. In such cases, you can implement a serializer with JIT disabled and directly
override the write and read methods.
This approach:
@Override
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 11/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
@Override
public boolean hasNext() {
return index < size;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return elements[index++];
}
};
}
@Override
public int size() {
return size;
}
@Override
public void write(MemoryBuffer buffer, IntList value) {
// Write size
buffer.writeVarUint32Small7(value.size());
@Override
public IntList read(MemoryBuffer buffer) {
// Read size
int size = buffer.readVarUint32Small7();
@Override
public Collection newCollection(MemoryBuffer buffer) {
throw new UnsupportedOperationException();
}
@Override
public IntList onCollectionRead(Collection collection) {
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 13/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
Key Points:
2. Direct Serialization:
3. Direct Deserialization:
4. Disabled JIT:
Set supportCodegenHook=false
Override write / read methods
Skip collection view pattern
Full control over serialization format
Usage Example:
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 14/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
// Serialize
byte[] bytes = fory.serialize(list);
// Deserialize
IntList newList = (IntList) fory.deserialize(bytes);
Remember that while this approach gives up some of Fory's optimizations, it can provide
better performance for specific use cases involving primitive types and direct array access.
Sometimes you may want to implement a serializer for a type that behaves like a collection
but isn't a standard Java Collection. This section demonstrates how to implement a
serializer for such types.
Here's an example:
class CustomCollectionLike {
private final Object[] elements;
private final int size;
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 15/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 16/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < size;
}
@Override
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return elements[index++];
}
};
}
@Override
public boolean add(Object element) {
if (writeIndex >= size) {
throw new IllegalStateException("Collection is full");
}
elements[writeIndex++] = element;
return true;
}
@Override
public int size() {
return size;
}
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 17/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
@Override
public Collection onCollectionWrite(MemoryBuffer buffer,
CustomCollectionLike value) {
buffer.writeVarUint32Small7(value.size());
return new CollectionView(value);
}
@Override
public Collection newCollection(MemoryBuffer buffer) {
int numElements = buffer.readVarUint32Small7();
setNumElements(numElements);
return new CollectionView(numElements);
}
@Override
public CustomCollectionLike onCollectionRead(Collection
collection) {
CollectionView view = (CollectionView) collection;
return new CustomCollectionLike(view.getElements(),
view.size());
}
}
Key takeways:
1. Collection Structure:
2. View Implementation:
3. Serializer Features:
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 18/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
4. Performance Aspects:
Note that this implementation provides better performance at the cost of flexibility. Consider
your specific use case when choosing this approach.
When true :
Enables optimized access to map elements and JIT compilation for better
performance
Direct serialization invocation and inline for map key-value items without dynamic
serializer dispatch cost.
Better performance for standard map types
Recommended for most maps
When false :
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 19/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
When implementing a Map serializer with JIT support, you can leverage Fory's existing
chunk-based binary format and map serialization infrastructure. The key is to properly
implement the onMapWrite and newMap methods to handle metadata while letting Fory
handle the map key-value serialization.
@Override
public Map onMapWrite(MemoryBuffer buffer, T value) {
// Write map size
buffer.writeVarUint32Small7(value.size());
// Write any additional map metadata here
return value;
}
@Override
public Map newMap(MemoryBuffer buffer) {
// Read map size
int numElements = buffer.readVarUint32Small7();
setNumElements(numElements);
// Create and return new map instance
T map = (T) new HashMap(numElements);
fory.getRefResolver().reference(map);
return map;
}
}
Note that please invoke setNumElements when implementing newMap to let fory know
how many elements to deserialize.
Sometimes you may need complete control over the serialization process, or your map type
might have special requirements that don't fit the standard patterns. In such cases, you can
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 20/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
This approach:
Here's an example:
@Override
public Set<Entry<String, Integer>> entrySet() {
Set<Entry<String, Integer>> entries = new HashSet<>();
for (String key : keys) {
entries.add(new SimpleEntry<>(key, fixedValue));
}
return entries;
}
@Override
public Integer get(Object key) {
return keys.contains(key) ? fixedValue : null;
}
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 21/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
@Override
public void write(MemoryBuffer buffer, FixedValueMap value) {
// Write the fixed value
buffer.writeInt32(value.getFixedValue());
// Write the number of keys
buffer.writeVarUint32Small7(value.getKeys().size());
// Write each key
for (String key : value.getKeys()) {
buffer.writeString(key);
}
}
@Override
public FixedValueMap read(MemoryBuffer buffer) {
// Read the fixed value
int fixedValue = buffer.readInt32();
// Read the number of keys
int size = buffer.readVarUint32Small7();
Set<String> keys = new HashSet<>(size);
for (int i = 0; i < size; i++) {
keys.add(buffer.readString());
}
return new FixedValueMap(keys, fixedValue);
}
@Override
public FixedValueMap onMapRead(Map map) {
throw new UnsupportedOperationException();
}
@Override
public FixedValueMap onMapCopy(Map map) {
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 22/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
Key Points:
1. Disable Codegen:
2. Write Method:
3. Read Method:
4. Unused Methods:
Trade-offs
1. Advantages:
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 23/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
2. Disadvantages:
No JIT optimization
Potentially lower performance
Manual handling of all serialization
More code to maintain
Remember that disabling codegen means giving up some performance optimizations that
Fory provides. Only use this approach when the standard map serialization pattern doesn't
meet your needs.
Sometimes you may want to implement a serializer for a type that behaves like a map but
isn't a standard Java map. This section demonstrates how to implement a serializer for such
types.
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 24/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 25/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
@Override
public Set<Entry<Object, Object>> entrySet() {
return new AbstractSet<Entry<Object, Object>>() {
@Override
public Iterator<Entry<Object, Object>> iterator() {
return new Iterator<Entry<Object, Object>>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < size;
}
@Override
public Entry<Object, Object> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
final int currentIndex = index++;
return new SimpleEntry<>(
keyArray[currentIndex],
valueArray[currentIndex]
);
}
};
}
@Override
public int size() {
return size;
}
};
}
@Override
public Object put(Object key, Object value) {
if (writeIndex >= size) {
throw new IllegalStateException("Map is full");
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 26/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
}
keyArray[writeIndex] = key;
valueArray[writeIndex] = value;
writeIndex++;
return null;
}
@Override
public Map onMapWrite(MemoryBuffer buffer, CustomMapLike value) {
buffer.writeVarUint32Small7(value.size());
// Return a zero-copy view using the same underlying arrays
return new MapView(value);
}
@Override
public Map newMap(MemoryBuffer buffer) {
int numElements = buffer.readVarUint32Small7();
setNumElements(numElements);
// Create a view with new arrays for deserialization
return new MapView(numElements);
}
@Override
public CustomMapLike onMapRead(Map map) {
MapView view = (MapView) map;
// Just pass the arrays directly - no copying needed
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 27/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
@Override
public CustomMapLike onMapCopy(Map map) {
MapView view = (MapView) map;
// Just pass the arrays directly - no copying needed
return new CustomMapLike(view.getKeyArray(),
view.getValueArray(), view.size());
}
}
Do not disable class registration unless you can ensure your environment is secure.
Malicious code in init/equals/hashCode can be executed when deserializing
unknown/untrusted types when this option disabled.
Class registration can not only reduce security risks, but also avoid classname serialization
cost.
Note that class registration order is important, serialization and deserialization peer should
have same registration order.
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 29/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
If there are no duplicate name for type, namespace can be left as empty to reduce
serialized size.
Do not use this API to register class since it will increase serialized size a lot
compared to register class by id
Zero-Copy Serialization
import org.apache.fory.*;
import org.apache.fory.config.*;
import org.apache.fory.serializer.BufferObject;
import org.apache.fory.memory.MemoryBuffer;
import java.util.*;
import java.util.stream.Collectors;
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 30/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
// mvn exec:java -
Dexec.mainClass="io.ray.fory.examples.ZeroCopyExample"
public static void main(String[] args) {
List<Object> list = Arrays.asList("str", new byte[1000], new
int[100], new double[100]);
Collection<BufferObject> bufferObjects = new ArrayList<>();
byte[] bytes = fory.serialize(list, e -> !bufferObjects.add(e));
List<MemoryBuffer> buffers = bufferObjects.stream()
.map(BufferObject::toBuffer).collect(Collectors.toList());
System.out.println(fory.deserialize(bytes, buffers));
}
}
Meta Sharing
Fory supports share type metadata (class name, field name, final field type information,
etc.) between multiple serializations in a context (ex. TCP connection), and this information
will be sent to the peer during the first serialization in the context. Based on this metadata,
the peer can rebuild the same deserializer, which avoids transmitting metadata for
subsequent serializations and reduces network traffic pressure and supports type
forward/backward compatibility automatically.
// Fory.builder()
// .withLanguage(Language.JAVA)
// .withRefTracking(false)
// // share meta across serialization.
// .withMetaContextShare(true)
// Not thread-safe fory.
MetaContext context = xxx;
fory.getSerializationContext().setMetaContext(context);
byte[] bytes = fory.serialize(o);
// Not thread-safe fory.
MetaContext context = xxx;
fory.getSerializationContext().setMetaContext(context);
fory.deserialize(bytes);
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 31/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
// Thread-safe fory
fory.setClassLoader(beanA.getClass().getClassLoader());
byte[] serialized = fory.execute(
f -> {
f.getSerializationContext().setMetaContext(context);
return f.serialize(beanA);
}
);
// thread-safe fory
fory.setClassLoader(beanA.getClass().getClassLoader());
Object newObj = fory.execute(
f -> {
f.getSerializationContext().setMetaContext(context);
return f.deserialize(serialized);
}
);
If metadata sharing is not enabled, the new class data will be skipped and an
NonexistentSkipClass stub object will be returned.
Notes:
1. This mapping will execute a deep copy, all mapped fields are serialized into binary
and deserialized from that binary to map into another type.
2. All struct types must be registered with same ID, otherwise Fory can not mapping
to correct struct type. Be careful when you use Fory#register(Class) , because
fory will allocate an auto-grown ID which might be inconsistent if you register
classes with different order between Fory instance.
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 32/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
.withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory();
static ThreadSafeFory fory2 = Fory.builder()
.withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory();
static {
fory1.register(Struct1.class);
fory2.register(Struct2.class);
}
Migration
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 33/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
JDK migration
If you use jdk serialization before, and you can't upgrade your client and server at the same
time, which is common for online application. Fory provided an util method
org.apache.fory.serializer.JavaSerializer.serializedByJDK to check whether
the binary are generated by jdk serialization, you use following pattern to make exiting
serialization protocol-aware, then upgrade serialization to fory in an async rolling-up way:
if (JavaSerializer.serializedByJDK(bytes)) {
ObjectInputStream objectInputStream=xxx;
return objectInputStream.readObject();
} else {
return fory.deserialize(bytes);
}
Upgrade fory
Currently binary compatibility is ensured for minor versions only. For example, if you are
using fory v0.2.0 , binary compatibility will be provided if you upgrade to fory v0.2.1 . But if
upgrade to fory v0.4.1 , no binary compatibility are ensured. Most of the time there is no
need to upgrade fory to newer major version, the current version is fast and compact
enough, and we provide some minor fix for recent older versions.
But if you do want to upgrade fory for better performance and smaller size, you need to
write fory version as header to serialized data using code like following to keep binary
compatibility:
getFory is a method to load corresponding fory, you can shade and relocate different
version of fory to different package, and load fory by version.
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 34/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
If you upgrade fory by minor version, or you won't have data serialized by older fory, you
can upgrade fory directly, no need to versioning the data.
Trouble shooting
Class inconsistency and class version check
If you create fory without setting CompatibleMode to
org.apache.fory.config.CompatibleMode.COMPATIBLE , and you got a strange
serialization error, it may be caused by class inconsistency between serialization peer and
deserialization peer.
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 35/36
8/18/25, 6:02 PM Java Serialization Guide | Apache Fory
double f3;
}
.withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory();
https://fory.apache.org/docs/docs/guide/java_object_graph_guide 36/36