title: Basic Serialization sidebar_position: 1 id: basic-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
This page covers the Java xlang quickstart. Xlang mode is the default Java wire format and is the right first choice for cross-language payloads.
For a single-threaded xlang Fory instance, set the mode explicitly:
import org.apache.fory.Fory; Fory fory = Fory.builder() .withXlang(true) .requireClassRegistration(true) .build();
For a thread-safe Fory instance, build ThreadSafeFory from the same builder:
import org.apache.fory.ThreadSafeFory; ThreadSafeFory fory = Fory.builder() .withXlang(true) .requireClassRegistration(true) .buildThreadSafeFory();
Default Java xlang mode also defaults to compatible schema mode, so independently deployed services can add and remove fields when their schema metadata remains compatible. Use withCompatible(false) only when every reader and writer always uses the same schema and you want faster serialization and smaller size. Use the compatible=false opt-out only after verifying that every language uses the same xlang schema, or when native types are generated from Fory schema IDL.
Register application classes with the same type identity on every peer. Numeric IDs are compact and fast, while name registration is easier to coordinate across independently owned services.
import org.apache.fory.annotation.ForyField; public class User { @ForyField(id = 0) public String name; @ForyField(id = 1) public int age; } Fory fory = Fory.builder() .withXlang(true) .requireClassRegistration(true) .build(); fory.register(User.class, "example", "User");
Use field IDs for long-lived schemas so field identity is stable even if Java field names change. See Schema Metadata for Java annotations, nullability, reference tracking, and enum metadata.
User user = new User(); user.name = "Alice"; user.age = 30; byte[] bytes = fory.serialize(user); User decoded = fory.deserialize(bytes, User.class);
When xlang bytes cross languages, every peer must register the same type identity and compatible field metadata. The shared rules live in Cross-Language Interoperability, and the Java-specific interoperability requirements are covered below.
For same-language Java/JVM traffic, native mode is usually the better fit:
Fory fory = Fory.builder() .withXlang(false) .build();
Native mode supports the broad Java object serialization surface, including JDK serialization hooks, object copy, and native-mode zero-copy buffers. See Native Serialization.
withRefTracking(true) preserves shared references and circular references.requireClassRegistration(true) keeps the default registered-type policy.withCompatible(false) only when every reader and writer uses the same schema and you want faster serialization and smaller size. For xlang payloads, use the compatible=false opt-out only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL.withAsyncCompilation(true) enables asynchronous serializer compilation where supported.The default xlang format is shared by all supported Fory implementations. The following sections cover its cross-language type mapping, type identity, and interoperability requirements.
Apache Fory™ xlang serialization is the Java wire mode for payloads that must be read by Python, Rust, Go, JavaScript/TypeScript, C++, C#, Swift, Dart, Scala, Kotlin, or another non-Java Fory implementation. Java defaults to xlang mode with compatible schema evolution, but examples set the mode explicitly so the payload contract is visible in code.
Use one long-lived Fory or ThreadSafeFory instance per configuration. Creating a Fory instance is expensive because Fory caches type metadata and generated serializers.
import org.apache.fory.Fory; Fory fory = Fory.builder() .withXlang(true) .requireClassRegistration(true) .withRefTracking(true) .build();
withRefTracking(true) is required only when the cross-language data model includes shared object identity or cycles. Disable it for value-shaped schemas.
Use Native Serialization instead when every writer and reader is Java and the payload should preserve Java-specific object behavior.
Types must be registered with consistent IDs or names across all languages. Fory supports two registration methods.
public record Person(String name, int age) {} // Numeric ID registration is compact and fast. fory.register(Person.class, 1); Person person = new Person("Alice", 30); byte[] bytes = fory.serialize(person); // bytes can be deserialized by Python, Rust, Go, etc.
Benefits: faster serialization and smaller binary size.
Trade-off: every service must coordinate IDs so the same logical type uses the same number.
public record Person(String name, int age) {} // Namespace/type-name registration is easier to coordinate across teams. fory.register(Person.class, "example", "Person"); Person person = new Person("Alice", 30); byte[] bytes = fory.serialize(person); // bytes can be deserialized by Python, Rust, Go, etc.
Benefits: less risk of numeric ID conflicts and easier management across independently owned services.
Trade-off: the payload includes string identity, so it is larger than ID-based registration.
The Java API also supports a single string type name, such as fory.register(Person.class, "example.Person"). Use the same logical identity on every peer.
import org.apache.fory.Fory; import java.nio.file.Files; import java.nio.file.Path; public record Person(String name, int age) {} public class Example { public static void main(String[] args) throws Exception { Fory fory = Fory.builder() .withXlang(true) .withRefTracking(true) .build(); // Register with the same logical name used by Python. fory.register(Person.class, "example.Person"); Person person = new Person("Bob", 25); byte[] bytes = fory.serialize(person); Files.write(Path.of("person.bin"), bytes); } }
import pyfory from dataclasses import dataclass @dataclass class Person: name: str age: pyfory.Int32 fory = pyfory.Fory(xlang=True, ref=True) # Register with the same name as Java. fory.register_type(Person, name="example.Person") with open("person.bin", "rb") as input_file: person = fory.deserialize(input_file.read()) print(f"{person.name}, {person.age}") # Output: Bob, 25
Xlang mode supports circular and shared references when reference tracking is enabled:
public class Node { public String value; public Node next; public Node parent; } Fory fory = Fory.builder() .withXlang(true) .withRefTracking(true) .build(); fory.register(Node.class, "example.Node"); Node node1 = new Node(); node1.value = "A"; Node node2 = new Node(); node2.value = "B"; node1.next = node2; node2.parent = node1; byte[] bytes = fory.serialize(node1); // Python/Rust/Go can correctly deserialize this with circular references preserved
Not all Java types have equivalents in other languages. When using xlang mode:
int, long, double, String) for maximum compatibility.List, Map, Set) instead of language-specific collections.Float16, BFloat16, Float16List, BFloat16List) for 16-bit float payloads.Float16[], BFloat16[], Float16List, and BFloat16List as list<T> carriers by default; use @ArrayType when the schema must be array<float16> or array<bfloat16>.Optional, BigDecimal, and EnumSet unless every target language has an agreed mapping.Java primitive arrays are dense array<T> carriers, except plain byte[], which defaults to bytes. General Java collections and Fory primitive-list carriers such as Int32List, Float16List, and BFloat16List use list<T> unless the field has explicit @ArrayType metadata.
| Fory schema | Java field shape |
|---|---|
list<int32> | List<Integer> or Int32List |
array<bool> | boolean[] |
array<int8> | @Int8Type byte[] type-use |
array<int16> | short[] |
array<int32> | int[] |
array<int64> | long[] |
array<uint8> | @UInt8Type byte[] type-use |
array<uint16> | @UInt16Type short[] type-use |
array<uint32> | @UInt32Type int[] type-use |
array<uint64> | @UInt64Type long[] type-use |
array<float16> | Float16Array or @Float16Type short[] |
array<bfloat16> | BFloat16Array or @BFloat16Type short[] |
array<float32> | float[] |
array<float64> | double[] |
Prefer type-use syntax for primitive-array annotations:
private @UInt32Type int[] ids; private @BFloat16Type short[] values;
public record UserData( String name, // compatible int age, // compatible List<String> tags, // compatible Map<String, Integer> scores // compatible ) {}
public record UserData( Optional<String> name, // not cross-language compatible BigDecimal balance, // limited support EnumSet<Status> statuses // Java-specific collection ) {}
Xlang mode has additional overhead compared to Java native mode:
For best performance:
withRefTracking(false))withXlang(false)) when only Java serialization is neededimport org.apache.fory.*; import org.apache.fory.config.*; import java.util.*; public class Example1 { public static void main(String[] args) { Fory fory = Fory.builder().withXlang(true).build(); List<Object> list = ofArrayList(true, false, "str", -1.1, 1, new int[100], new double[20]); byte[] bytes = fory.serialize(list); // bytes can be deserialized by other languages fory.deserialize(bytes); Map<Object, Object> map = new HashMap<>(); map.put("k1", "v1"); map.put("k2", list); map.put("k3", -1); bytes = fory.serialize(map); // bytes can be deserialized by other languages fory.deserialize(bytes); } }
import org.apache.fory.*; import org.apache.fory.config.*; import java.util.*; public class Example2 { public static class SomeClass1 { Object f1; Map<Byte, Integer> f2; } public static class SomeClass2 { Object f1; String f2; List<Object> f3; Map<Byte, Integer> f4; Byte f5; Short f6; Integer f7; Long f8; Float f9; Double f10; short[] f11; List<Short> f12; } public static Object createObject() { SomeClass1 obj1 = new SomeClass1(); obj1.f1 = true; obj1.f2 = ofHashMap((byte) -1, 2); SomeClass2 obj = new SomeClass2(); obj.f1 = obj1; obj.f2 = "abc"; obj.f3 = ofArrayList("abc", "abc"); obj.f4 = ofHashMap((byte) 1, 2); obj.f5 = Byte.MAX_VALUE; obj.f6 = Short.MAX_VALUE; obj.f7 = Integer.MAX_VALUE; obj.f8 = Long.MAX_VALUE; obj.f9 = 1.0f / 2; obj.f10 = 1 / 3.0; obj.f11 = new short[]{(short) 1, (short) 2}; obj.f12 = ofArrayList((short) -1, (short) 4); return obj; } // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.Example2" public static void main(String[] args) { Fory fory = Fory.builder().withXlang(true).build(); fory.register(SomeClass1.class, "example.SomeClass1"); fory.register(SomeClass2.class, "example.SomeClass2"); byte[] bytes = fory.serialize(createObject()); // bytes can be deserialized by other languages System.out.println(fory.deserialize(bytes)); } }