Skip to main content
Version: 1.3

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=false or 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';

()
class Person {
Person();

String name = '';

(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:

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.