Usage
This section provides quick examples for getting started with Apache Fory™.
Native Serialization
Always use native mode when working with a single language. Native mode delivers optimal performance by avoiding the type metadata overhead required for cross-language compatibility.
Xlang mode introduces additional metadata encoding costs and restricts serialization to types that are common across all supported languages. Language-specific types will be rejected during serialization in xlang-mode.
Java Serialization
When you don't need cross-language support, use Java mode for optimal performance.
import org.apache.fory.*;
import org.apache.fory.config.*;
public class Example {
public static class Person {
String name;
int age;
}
public static void main(String[] args) {
// Create Fory instance - should be reused across serializations
BaseFory fory = Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(true)
// replace `build` with `buildThreadSafeFory` for Thread-Safe Usage
.build();
// Register your classes (required when class registration is enabled)
fory.register(Person.class);
// Serialize
Person person = new Person();
person.name = "chaokunyang";
person.age = 28;
byte[] bytes = fory.serialize(person);
Person result = (Person) fory.deserialize(bytes);
System.out.println(result.name + " " + result.age); // Output: chaokunyang 28
}
}
For detailed Java usage including compatibility modes, compression, and advanced features, see Java Serialization Guide.
Python Serialization
Python native mode provides a high-performance drop-in replacement for pickle/cloudpickle with better speed and compatibility.
from dataclasses import dataclass
import pyfory
@dataclass
class Person:
name: str
age: pyfory.int32
# Create Fory instance - should be reused across serializations
fory = pyfory.Fory()
# Register your classes (required when class registration is enabled)
fory.register_type(Person)
person = Person(name="chaokunyang", age=28)
data = fory.serialize(person)
result = fory.deserialize(data)
print(result.name, result.age) # Output: chaokunyang 28
For detailed Python usage including type hints, compatibility modes, and advanced features, see Python Guide.
Scala Serialization
Scala native mode provides optimized serialization for Scala-specific types including case classes, collections, and Option types.
import org.apache.fory.Fory
import org.apache.fory.config.Language
import org.apache.fory.serializer.scala.ScalaSerializers
case class Person(name: String, age: Int)
object Example {
def main(args: Array[String]): Unit = {
// Create Fory instance - should be reused across serializations
val fory = Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(true)
.build()
// Register Scala serializers for Scala-specific types
ScalaSerializers.registerSerializers(fory)
// Register your case classes
fory.register(classOf[Person])
val bytes = fory.serialize(Person("chaokunyang", 28))
val result = fory.deserialize(bytes).asInstanceOf[Person]
println(s"${result.name} ${result.age}") // Output: chaokunyang 28
}
}
For detailed Scala usage including collection serialization and integration patterns, see Scala Guide.
Kotlin Serialization
Kotlin native mode provides optimized serialization for Kotlin-specific types including data classes, nullable types, and Kotlin collections.
import org.apache.fory.Fory
import org.apache.fory.config.Language
import org.apache.fory.serializer.kotlin.KotlinSerializers
data class Person(val name: String, val age: Int)
fun main() {
// Create Fory instance - should be reused across serializations
val fory = Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(true)
.build()
// Register Kotlin serializers for Kotlin-specific types
KotlinSerializers.registerSerializers(fory)
// Register your data classes
fory.register(Person::class.java)
val bytes = fory.serialize(Person("chaokunyang", 28))
val result = fory.deserialize(bytes) as Person
println("${result.name} ${result.age}") // Output: chaokunyang 28
}
For detailed Kotlin usage including null safety and default value support, see kotlin/README.md.
Cross-Language Serialization
Only use xlang mode when you need cross-language data exchange. Xlang mode adds type metadata overhead for cross-language compatibility and only supports types that can be mapped across all languages. For single-language use cases, always prefer native mode for better performance.
The following examples demonstrate serializing a Person object across Java and Rust. For other languages (Python, Go, JavaScript, etc.), simply set the language mode to XLANG and follow the same pattern.
Java
import org.apache.fory.*;
import org.apache.fory.config.*;
public class XlangExample {
public record Person(String name, int age) {}
public static void main(String[] args) {
// Create Fory instance with XLANG mode
Fory fory = Fory.builder()
.withLanguage(Language.XLANG)
.build();
// Register with cross-language type id/name
fory.register(Person.class, 1);
// fory.register(Person.class, "example.Person");
Person person = new Person("chaokunyang", 28);
byte[] bytes = fory.serialize(person);
// bytes can be deserialized by Rust, Python, Go, or other languages
Person result = (Person) fory.deserialize(bytes);
System.out.println(result.name + " " + result.age); // Output: chaokunyang 28
}
}
Rust
use fory::{Fory, ForyObject};
#[derive(ForyObject, Debug)]
struct Person {
name: String,
age: i32,
}
fn main() -> Result<(), Error> {
let mut fory = Fory::default();
fory.register::<Person>(1)?;
// fory.register_by_name::<Person>("example.Person")?;
let person = Person {
name: "chaokunyang".to_string(),
age: 28,
};
let bytes = fory.serialize(&person);
// bytes can be deserialized by Java, Python, Go, or other languages
let result: Person = fory.deserialize(&bytes)?;
println!("{} {}", result.name, result.age); // Output: chaokunyang 28
}
Golang
type Person struct {
name: string
age: i32
}
fory := fory.NewFory(true)
fory.Register(Person{}, 1)
person := Person{"chaokunyang", 28}
bytes, err := fory.Marshal(person)
var p Person
err = fory.Unmarshal(bytes, &p)
JavaScript
import Fory, { Type } from "@apache-fory/fory";
/**
* @apache-fory/hps use v8's fast-calls-api that can be called directly by jit, ensure that the version of Node is 20 or above.
* Experimental feature, installation success cannot be guaranteed at this moment
* If you are unable to install the module, replace it with `const hps = null;`
**/
import hps from "@apache-fory/hps";
// Now we describe data structures using JSON, but in the future, we will use more ways.
const description = Type.object("example.Person", {
name: Type.string(),
age: Type.int32(),
});
const fory = new Fory({ hps });
const { serialize, deserialize } = fory.registerSerializer(description);
const input = serialize({ name: "chaokunyang", age: 28 });
const result = deserialize(input);
console.log(result);
Key Points
- Use
Language.XLANGmode in all languages - Register types with consistent IDs or names across all languages:
- By ID (
fory.register(Person.class, 1)): Faster serialization, more compact encoding, but requires coordination to avoid ID conflicts - By name (
fory.register(Person.class, "example.Person")): More flexible, less prone to conflicts, easier to manage across teams, but slightly larger encoding
- By ID (
- Type IDs/names must match across all languages for successful deserialization
- Only use types that have cross-language mappings (see Type Mapping)
For examples with circular references, shared references, and polymorphism across languages, see:
- Cross-Language Serialization Guide
- Java Serialization Guide - Cross Language
- Python Guide - Cross Language
Row Format Encoding
Row format provides zero-copy random access to serialized data, making it ideal for analytics workloads and data processing pipelines.
Java
import org.apache.fory.format.*;
import java.util.*;
import java.util.stream.*;
public class Bar {
String f1;
List<Long> f2;
}
public class Foo {
int f1;
List<Integer> f2;
Map<String, Integer> f3;
List<Bar> f4;
}
RowEncoder<Foo> encoder = Encoders.bean(Foo.class);
Foo foo = new Foo();
foo.f1 = 10;
foo.f2 = IntStream.range(0, 1000000).boxed().collect(Collectors.toList());
foo.f3 = IntStream.range(0, 1000000).boxed().collect(Collectors.toMap(i -> "k"+i, i -> i));
List<Bar> bars = new ArrayList<>(1000000);
for (int i = 0; i < 1000000; i++) {
Bar bar = new Bar();
bar.f1 = "s" + i;
bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList());
bars.add(bar);
}
foo.f4 = bars;
// Serialize to row format (can be zero-copy read by Python)
BinaryRow binaryRow = encoder.toRow(foo);
// Deserialize entire object
Foo newFoo = encoder.fromRow(binaryRow);
// Zero-copy access to nested fields without full deserialization
BinaryArray binaryArray2 = binaryRow.getArray(1); // Access f2 field
BinaryArray binaryArray4 = binaryRow.getArray(3); // Access f4 field
BinaryRow barStruct = binaryArray4.getStruct(10); // Access 11th Bar element
long value = barStruct.getArray(1).getInt64(5); // Access nested value
// Partial deserialization
RowEncoder<Bar> barEncoder = Encoders.bean(Bar.class);
Bar newBar = barEncoder.fromRow(barStruct);
Bar newBar2 = barEncoder.fromRow(binaryArray4.getStruct(20));
Python
from dataclasses import dataclass
from typing import List, Dict
import pyarrow as pa
import pyfory
@dataclass
class Bar:
f1: str
f2: List[pa.int64]
@dataclass
class Foo:
f1: pa.int32
f2: List[pa.int32]
f3: Dict[str, pa.int32]
f4: List[Bar]
encoder = pyfory.encoder(Foo)
foo = Foo(
f1=10,
f2=list(range(1000_000)),
f3={f"k{i}": i for i in range(1000_000)},
f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1000_000)]
)
# Serialize to row format
binary: bytes = encoder.to_row(foo).to_bytes()
# Zero-copy random access without full deserialization
foo_row = pyfory.RowData(encoder.schema, binary)
print(foo_row.f2[100000]) # Access element directly
print(foo_row.f4[100000].f1) # Access nested field
print(foo_row.f4[200000].f2[5]) # Access deeply nested field
For more details on row format, see Row Format Guide.