Usage
This section provides quick examples for getting started with Apache Fory™.
Choose A Mode
Apache Fory™ has two wire modes:
- Xlang mode is the default and the portable format for payloads shared across languages. Use it for cross-language services and for runtimes that expose only xlang mode: Dart, JavaScript/TypeScript, C#, and Swift.
- Native mode is selected with
xlang=falseor the equivalent builder option in Java, Scala, Kotlin, Python, C++, Go, and Rust. Use it for same-language traffic because it follows the runtime's native type system, supports a broader language-specific object surface, and is optimized for that runtime.
Xlang/default usage uses schema-compatible mode by default. Native mode uses schema-consistent payloads by default unless compatible mode is enabled explicitly.
Xlang Mode
Use xlang mode when bytes need to cross runtime boundaries. Register custom types with the same numeric ID or namespace/type name on every peer.
Dual-mode runtimes set the xlang option explicitly in the examples below. Dart, JavaScript/TypeScript, C#, and Swift are xlang-only, so their examples do not show an xlang switch.
Java
import org.apache.fory.Fory;
public class XlangExample {
public record Person(String name, int age) {}
public static void main(String[] args) {
Fory fory = Fory.builder()
.withXlang(true)
.build();
fory.register(Person.class, "example", "Person");
Person person = new Person("chaokunyang", 28);
byte[] bytes = fory.serialize(person);
Person result = (Person) fory.deserialize(bytes);
System.out.println(result.name() + " " + result.age());
}
}
Python
from dataclasses import dataclass
import pyfory
@dataclass
class Person:
name: str
age: pyfory.Int32
fory = pyfory.Fory(xlang=True)
fory.register(Person, typename="example.Person")
person = Person(name="chaokunyang", age=28)
data = fory.serialize(person)
result = fory.deserialize(data)
print(result.name, result.age)
Dart
import 'package:fory/fory.dart';
part 'person.fory.dart';
@ForyStruct()
class Person {
Person();
String name = '';
@ForyField(type: Int32Type())
int age = 0;
}
void main() {
final fory = Fory();
PersonFory.register(
fory,
Person,
namespace: 'example',
typeName: 'Person',
);
final person = Person()
..name = 'chaokunyang'
..age = 28;
final bytes = fory.serialize(person);
final result = fory.deserialize<Person>(bytes);
print('${result.name} ${result.age}');
}
Go
package main
import (
"fmt"
"github.com/apache/fory/go/fory"
)
type Person struct {
Name string
Age int32
}
func main() {
f := fory.New(fory.WithXlang(true))
if err := f.RegisterStruct(Person{}, 1); err != nil {
panic(err)
}
person := &Person{Name: "chaokunyang", Age: 28}
data, err := f.Serialize(person)
if err != nil {
panic(err)
}
var result Person
if err := f.Deserialize(data, &result); err != nil {
panic(err)
}
fmt.Printf("%s %d\n", result.Name, result.Age)
}
Rust
use fory::{Error, Fory, ForyObject};
#[derive(ForyObject, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
}
fn main() -> Result<(), Error> {
let mut fory = Fory::builder().xlang(true).build();
fory.register_by_name::<Person>("example", "Person")?;
let person = Person {
name: "chaokunyang".to_string(),
age: 28,
};
let bytes = fory.serialize(&person)?;
let result: Person = fory.deserialize(&bytes)?;
assert_eq!(person, result);
Ok(())
}
C++
#include <cassert>
#include <string>
#include "fory/serialization/fory.h"
using namespace fory::serialization;
struct Person {
std::string name;
int32_t age;
bool operator==(const Person &other) const {
return name == other.name && age == other.age;
}
FORY_STRUCT(Person, name, age);
};
int main() {
auto fory = Fory::builder().xlang(true).build();
fory.register_struct<Person>(1);
Person person{"chaokunyang", 28};
auto bytes = fory.serialize(person).value();
auto result = fory.deserialize<Person>(bytes).value();
assert(person == result);
return 0;
}
Scala
import org.apache.fory.Fory
import org.apache.fory.scala.ForyScala
case class Person(name: String, age: Int)
object Example {
def main(args: Array[String]): Unit = {
val fory: Fory = ForyScala.builder()
.withXlang(true)
.build()
fory.register(classOf[Person])
val bytes = fory.serialize(Person("chaokunyang", 28))
val result = fory.deserialize(bytes).asInstanceOf[Person]
println(s"${result.name} ${result.age}")
}
}
Kotlin
import org.apache.fory.ThreadSafeFory
import org.apache.fory.kotlin.ForyKotlin
data class Person(val name: String, val age: Int)
fun main() {
val fory: ThreadSafeFory = ForyKotlin.builder()
.withXlang(true)
.requireClassRegistration(true)
.buildThreadSafeFory()
fory.register(Person::class.java)
val bytes = fory.serialize(Person("chaokunyang", 28))
val result = fory.deserialize(bytes) as Person
println("${result.name} ${result.age}")
}
JavaScript / TypeScript
import Fory, { Type } from "@apache-fory/core";
const personType = Type.struct(
{ typeName: "example.Person" },
{
name: Type.string(),
age: Type.int32(),
},
);
const fory = new Fory();
const { serialize, deserialize } = fory.register(personType);
const payload = serialize({ name: "chaokunyang", age: 28 });
const result = deserialize(payload);
console.log(result);
C#
using Apache.Fory;
[ForyObject]
public sealed class Person
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
Fory fory = Fory.Builder().Build();
fory.Register<Person>(1);
Person person = new() { Name = "chaokunyang", Age = 28 };
byte[] data = fory.Serialize(person);
Person result = fory.Deserialize<Person>(data);
Console.WriteLine($"{result.Name} {result.Age}");
Swift
import Fory
@ForyStruct
struct Person: Equatable {
var name: String = ""
var age: Int32 = 0
}
let fory = Fory()
fory.register(Person.self, id: 1)
let person = Person(name: "chaokunyang", age: 28)
let data = try fory.serialize(person)
let result: Person = try fory.deserialize(data)
print("\(result.name) \(result.age)")
For more cross-language rules and examples, see:
- Cross-Language Serialization Guide
- Java Guide
- Python Guide
- Dart Guide
- Go Guide
- Rust Guide
- C++ Guide
- C# Guide
- Swift Guide
Native Mode
Use native mode only when every reader and writer is the same runtime family. Native mode supports broader language-specific object models than portable xlang mappings and is optimized for the owning runtime.
Java and Python native modes are first-class same-language entry points. Use Java native mode when replacing JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers payloads. Use Python native mode when replacing pickle or cloudpickle for Python-only payloads.
Dart, JavaScript/TypeScript, C#, and Swift do not expose native mode.
Java
Fory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(true)
.build();
Register Java classes and use serialize / deserialize as usual. See the Java Guide for Java object hooks, Externalizable, dynamic object graphs, object copy, and Java native-mode zero-copy buffers.
Python
import pyfory
fory = pyfory.Fory(xlang=False, ref=False, strict=True)
Register Python classes and use serialize / deserialize as usual. See the Python Guide for native-mode pickle replacement behavior and security settings.
Go
f := fory.New(fory.WithXlang(false))
Use native mode for Go-only structs, pointers, interfaces, and Go-specific type behavior. See the Go Guide for struct tags and native-mode configuration.
Rust
let mut fory = Fory::builder().xlang(false).build();
Use native mode for Rust-only payloads that rely on Rust-specific object behavior. See the Rust Guide for derive, references, and supported types.
C++
auto fory = Fory::builder().xlang(false).build();
Use native mode for C++-only traffic that does not need portable xlang type mappings. See the C++ Guide for FORY_STRUCT, configuration, and schema metadata.
Scala
val fory = ForyScala.builder()
.withXlang(false)
.build()
Use native mode for Scala/JVM-only traffic that needs Scala case classes, collections, tuples, options, or enums on the JVM runtime path. See the Scala Guide.
Kotlin
val fory = ForyKotlin.builder()
.withXlang(false)
.requireClassRegistration(true)
.buildThreadSafeFory()
Use native mode for Kotlin/JVM-only traffic that needs Kotlin data classes, nullable types, ranges, unsigned values, or Kotlin collections on the JVM runtime path. See the Kotlin Guide.
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 Java Row Format Guide or Python Row Format Guide.