fory

1.2.0

A blazingly fast multi-language serialization framework for idiomatic domain objects, schema IDL, and cross-language data exchange.
apache/fory

What's New

v1.2.0

2026-06-16T12:22:16Z

Highlights

  • Expanded generated gRPC support across Go, Rust, Kotlin, Scala, C#, and JavaScript, including Node.js and browser gRPC-Web support for JavaScript.
  • Improved cross-language compatibility with refined register-by-name APIs, compatible scalar read conversions, and default compatible mode for native serialization.
  • Strengthened Java platform support by adding Java 9/16 module-info generation and removing sun.misc.Unsafe usage for JDK 25.
  • Improved runtime safety and robustness with additional read checks, deflater leak fixes, and safer serializer/type-info error handling.
  • Optimized compatible-mode and row-format performance through faster compatible reads, compact row layout caching, and inlined custom-codec dispatch.
  • Enhanced compiler output quality across Rust, C++, and service generation with better identifier escaping, name-collision handling, nested container reference handling, and map code generation.

Java 25+ Without sun.misc.Unsafe

JDK 25 continues the platform shift away from sun.misc.Unsafe. Fory 1.2.0
adds a Java 25 multi-release runtime path so applications can run on JDK 25+
without resolving sun.misc.Unsafe from Fory's active class graph.

Older JDKs keep the existing fast paths. On JDK 25+, Fory uses replacement
classes backed by supported JVM mechanisms such as VarHandle, MethodHandle,
arrays, and ByteBuffer. Classes that previously depended on constructor
bypassing should provide an accessible no-arg constructor, use records, or
register a custom serializer.

Compatible Scalar Field Reads

Compatible mode already allows readers and writers to add, remove, and reorder
fields. Fory 1.2.0 extends that model to selected scalar type changes: when a
matched top-level field changes between boolean, string, numeric, and decimal
types, the reader can deserialize the value if the conversion is lossless.

Examples include reading "123" as an integer field, reading 1 or 0 as a
boolean field, reading booleans as 1/0, reading numbers or decimals as
canonical strings, and widening or narrowing numeric values only when no range
or precision is lost. Invalid strings, out-of-range values, lossy float/integer
conversions, and reference-tracked scalar type changes fail during
deserialization. The conversion applies to matched compatible fields, not to
root values or collection elements.

The examples below show Rust and Java using an int64 writer field and a
String reader field. The same compatible scalar field conversion is supported
across Fory's compatible-mode runtimes: Java, Python, Rust, C++, Go, C#, Swift,
Dart, JavaScript/TypeScript, Kotlin, and Scala. Compatible mode is enabled by
default in the Java and Python runtimes for both xlang and native serialization.

Rust example:

use fory::{Fory, ForyStruct};

#[derive(ForyStruct)]
struct MetricV1 {
    value: i64,
}

#[derive(ForyStruct)]
struct MetricV2 {
    value: String,
}

let mut writer = Fory::builder().xlang(true).compatible(true).build();
writer.register_by_name::<MetricV1>("example.Metric")?;

let mut reader = Fory::builder().xlang(true).compatible(true).build();
reader.register_by_name::<MetricV2>("example.Metric")?;

let bytes = writer.serialize(&MetricV1 { value: 42 })?;
let value: MetricV2 = reader.deserialize(&bytes)?;
assert_eq!(value.value, "42");

Java example:

public class MetricV1 {
  public long value;
}

public class MetricV2 {
  public String value;
}

Fory writer = Fory.builder().withXlang(true).withCompatible(true).build();
writer.register(MetricV1.class, "example", "Metric");

Fory reader = Fory.builder().withXlang(true).withCompatible(true).build();
reader.register(MetricV2.class, "example", "Metric");

MetricV1 source = new MetricV1();
source.value = 42L;
byte[] bytes = writer.serialize(source);
MetricV2 value = reader.deserialize(bytes, MetricV2.class);
assert value.value.equals("42");

The same rule works in the other direction, for example reading a String
field value such as "42" as int64, when the string uses Fory's strict
finite decimal grammar and the target range can represent the value exactly.

Generated gRPC Support

Fory 1.2.0 expands compiler-generated gRPC service companions. The generated
services use standard gRPC transports, channels, deadlines, metadata,
interceptors, status codes, and streaming shapes, while request and response
objects are encoded with Fory instead of protobuf message bytes. Use this mode
when both sides of the RPC are generated from the same Fory IDL, protobuf IDL,
or FlatBuffers IDL and you want gRPC operational semantics with Fory payload
encoding.

Generated gRPC support now covers Java, Python, Go, Rust, C#, Scala, Kotlin,
and JavaScript/TypeScript. JavaScript includes Node.js gRPC support and browser
gRPC-Web client generation. Only Rust and Java snippets are shown below; the
other supported languages provide the same Fory-backed service companion model
without duplicating code here.

The examples below use this shared schema:

package demo.greeter;

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string reply = 1;
}

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

Rust generation emits tonic-based service API and binding modules:

use demo_greeter::{HelloReply, HelloRequest};
use demo_greeter_service::Greeter;
use demo_greeter_service_grpc::greeter_client::GreeterClient;
use demo_greeter_service_grpc::greeter_server::GreeterServer;

tonic::transport::Server::builder()
    .add_service(GreeterServer::new(MyGreeter::default()))
    .serve(addr)
    .await?;

let mut client = GreeterClient::connect("http://[::1]:50051").await?;
let reply = client.say_hello(HelloRequest { name: "Fory".into() }).await?;

Java generation emits grpc-java service bases, stubs, and Fory codecs:

final class GreeterService extends GreeterGrpc.GreeterImplBase {
  @Override
  public void sayHello(
      HelloRequest request, StreamObserver<HelloReply> responseObserver) {
    HelloReply reply = new HelloReply();
    reply.setReply("Hello, " + request.getName());
    responseObserver.onNext(reply);
    responseObserver.onCompleted();
  }
}

Server server = ServerBuilder.forPort(50051)
    .addService(new GreeterService())
    .build()
    .start();

GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
HelloRequest request = new HelloRequest();
request.setName("Fory");
HelloReply reply = stub.sayHello(request);

The generated gRPC companions intentionally do not make gRPC a hard dependency
of the core Fory language packages. Applications add the transport libraries
they use: grpc-java for Java and Scala, grpcio for Python, grpc-go for Go,
tonic/bytes for Rust, .NET gRPC packages for C#, @grpc/grpc-js or
grpc-web for JavaScript, and grpc-java/grpc-kotlin for Kotlin.

Features

Bug Fix

Other Improvements

New Contributors

Full Changelog: v1.1.0...v1.2.0

Apache Fory logo

Build Status Slack Channel X Maven Version Crates.io PyPI npm NuGet pub.dev

Apache Fory™ is a blazingly fast multi-language serialization framework for idiomatic domain objects, schema IDL, and cross-language data exchange.

https://fory.apache.org

Why Fory

Fory is built for fast, compact serialization across languages and implementations. It works with idiomatic objects in each language, supports shared schemas when you need a contract, and preserves object features such as shared and circular references.

  • Efficient Cross-Language Encoding: Exchange payloads across supported languages with compact binary encoding, metadata packing, schema evolution, shared/circular references, and polymorphic runtime types.
  • Domain Objects First: Serialize Java classes, Python dataclasses, Go structs, Rust/C++ structs, and generated or annotated model types directly. Preserve shared and circular references when object identity matters.
  • Reference-Aware Schema IDL: Support shared and circular references directly in the schema, alongside numbers, strings, lists, maps, arrays, enums, structs, and unions. Define schemas once, then generate native domain objects for each language without forcing wrapper types into user code.
  • Row-Format Random Access: Read fields, arrays, and nested values without rebuilding full objects, with zero-copy access and partial reads.
  • Optimized Implementations: Java JIT serializers and generated/static serializers in other language implementations keep hot paths fast and payloads compact.
  • Language And Platform Support: Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin, including GraalVM native image, Android, Dart VM/Flutter/web, and Node.js/browser JavaScript.

Performance

Benchmarks show Fory delivering higher throughput and smaller serialized payloads than common serialization frameworks on representative workloads. Java has the broadest comparison set; the other charts show language-specific results across supported languages.

Java Benchmarks

In Java serialization benchmarks, Fory reaches up to 170x the throughput of JDK serialization on selected workloads.

Java serialization throughput

Java deserialization throughput

Java xlang throughput

Python Benchmarks

Python serialization throughput

Rust Benchmarks

Rust serialization throughput

Benchmarks for C++, Go, JavaScript/TypeScript, C#, Swift, and Dart

C++ Benchmarks

C++ serialization throughput

Go Benchmarks

Go serialization throughput

JavaScript/TypeScript Benchmarks

JavaScript serialization throughput

C# Benchmarks

C# serialization throughput

Swift Benchmarks

Swift serialization throughput

Dart Benchmarks

Dart serialization throughput

Installation

Pick your language and run the package-manager command, or paste the dependency block into your build file.

Java

Maven:

<dependency>
  <groupId>org.apache.fory</groupId>
  <artifactId>fory-core</artifactId>
  <version>1.1.0</version>
</dependency>

Gradle:

implementation "org.apache.fory:fory-core:1.1.0"

On JDK25+, open java.lang.invoke to Fory. Use ALL-UNNAMED when Fory is on the classpath:

--add-opens=java.base/java.lang.invoke=ALL-UNNAMED

Use the Fory core module name when Fory is on the module path:

--add-opens=java.base/java.lang.invoke=org.apache.fory.core

Scala

sbt:

libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.1.0"

Kotlin

Gradle:

implementation("org.apache.fory:fory-kotlin:1.1.0")

Maven:

<dependency>
  <groupId>org.apache.fory</groupId>
  <artifactId>fory-kotlin</artifactId>
  <version>1.1.0</version>
</dependency>

Python

pip install pyfory

For row-format support:

pip install "pyfory[format]"

Rust

Cargo.toml:

[dependencies]
fory = "1.1.0"

C++

CMake:

include(FetchContent)
FetchContent_Declare(
  fory
  GIT_REPOSITORY https://github.com/apache/fory.git
  GIT_TAG v1.1.0
  SOURCE_SUBDIR cpp
)
FetchContent_MakeAvailable(fory)
target_link_libraries(my_app PRIVATE fory::serialization)

Bazel:

# MODULE.bazel
bazel_dep(name = "fory", version = "1.1.0")
git_override(module_name = "fory", remote = "https://github.com/apache/fory.git", commit = "v1.1.0")

# BUILD
deps = ["@fory//cpp/fory/serialization:fory_serialization"]

When building C++ with MSVC, enable the conforming preprocessor option /Zc:preprocessor; see the C++ installation guide for setup details.

See the C++ installation guide for complete CMake, Bazel, and source-build details.

Go

go get github.com/apache/fory/go/fory

JavaScript/TypeScript

npm install @apache-fory/core

For the Node.js string fast path:

npm install @apache-fory/core @apache-fory/hps

C#

dotnet add package Apache.Fory --version 1.1.0

Dart

dart pub add fory:^1.1.0
dart pub add dev:build_runner

Swift

Add Fory to Package.swift:

dependencies: [
  .package(url: "https://github.com/apache/fory.git", exact: "1.1.0")
],
targets: [
  .target(
    name: "YourTarget",
    dependencies: [.product(name: "Fory", package: "fory")]
  )
]

See the Swift guide for generated serializer setup.

Development From Source

See docs/DEVELOPMENT.md.

Snapshots for Java, Scala, and Kotlin are available from https://repository.apache.org/snapshots/ with the matching -SNAPSHOT version.

Choose Serialization Mode

Mode Use it when Start here
Xlang mode Data crosses language boundaries Cross-language guide
Native mode Producer and consumer are in the same language Language guide
Row format You need random field access or analytics-style partial reads Row format spec

For Java, Scala, Kotlin, Python, C++, Go, and Rust, use native mode for same-language traffic. It avoids xlang's cross-language type mapping and metadata constraints, stays closer to each language's native type system, and supports broader language-specific object graphs. Use it when both producer and consumer are in the same language family and you want the native object model rather than a portable cross-language schema.

For Java/JVM-only systems, native mode is the replacement path for JDK serialization, Kryo, FST, Hessian, and Java-only Protocol Buffers payloads. For Python-only systems, native mode is the replacement path for pickle and cloudpickle.

Compatible mode is Fory's schema-evolution mode. It writes the metadata readers and writers need to tolerate schema differences. It is the default for xlang mode and native mode in implementations that expose the option.

Use compatible mode when services deploy independently or when fields may be added or deleted over time. Set compatible mode to false only when every reader and writer always uses the same schema and you want faster serialization and smaller size. For xlang payloads, set compatible mode to false only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL.

For xlang, all peers must agree on type identity. Name-based registration is easier to read in examples. Numeric IDs are smaller and faster, but they require coordination across every reader and writer.

Cross-Language Serialization

Xlang mode writes the cross-language Fory wire format. Bytes produced by one language implementation can be read by another when every peer uses the same type identity, compatible mode setting, and field schema.

Java

import org.apache.fory.Fory;

public class Example {
  public static class Person {
    public String name;
    public 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();
    person.name = "Alice";
    person.age = 30;

    byte[] bytes = fory.serialize(person);
    Person decoded = (Person) fory.deserialize(bytes);
    System.out.println(decoded.name);
  }
}

Python

from dataclasses import dataclass

import pyfory

@dataclass
class Person:
    name: str
    age: pyfory.Int32

fory = pyfory.Fory(xlang=True)
fory.register_type(Person, name="example.Person")

data = fory.serialize(Person("Alice", 30))
person = fory.deserialize(data)
print(person.name)

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.RegisterStructByName(Person{}, "example.Person"); err != nil {
        panic(err)
    }

    data, _ := f.Serialize(&Person{Name: "Alice", Age: 30})
    var person Person
    if err := f.Deserialize(data, &person); err != nil {
        panic(err)
    }
    fmt.Println(person.Name)
}

Rust

use fory::{Error, Fory, ForyStruct};

#[derive(ForyStruct, 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 bytes = fory.serialize(&Person {
        name: "Alice".to_string(),
        age: 30,
    })?;
    let person: Person = fory.deserialize(&bytes)?;
    println!("{}", person.name);
    Ok(())
}

C++

#include "fory/serialization/fory.h"
#include <cstdint>
#include <iostream>
#include <string>

using namespace fory::serialization;

struct Person {
  std::string name;
  int32_t age;
};
FORY_STRUCT(Person, name, age);

int main() {
  auto fory = Fory::builder().xlang(true).build();
  fory.register_struct<Person>("example.Person");

  auto bytes = fory.serialize(Person{"Alice", 30}).value();
  Person person = fory.deserialize<Person>(bytes).value();
  std::cout << person.name << std::endl;
}

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 bytes = serialize({ name: "Alice", age: 30 });
const person = deserialize(bytes);
console.log(person.name);

C#

using Apache.Fory;

[ForyStruct]
public sealed class Person
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
}

Fory fory = Fory.Builder().Build();
fory.Register<Person>("example", "Person");

byte[] bytes = fory.Serialize(new Person { Name = "Alice", Age = 30 });
Person person = fory.Deserialize<Person>(bytes);
Console.WriteLine(person.Name);

C# always writes the xlang frame header, so there is no separate xlang builder flag.

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,
    name: 'example.Person',
  );

  final bytes = fory.serialize(Person()
    ..name = 'Alice'
    ..age = 30);
  final person = fory.deserialize<Person>(bytes);
  print(person.name);
}

Dart uses the xlang wire format directly. Generate the companion file before running:

dart run build_runner build --delete-conflicting-outputs

Swift

import Fory

@ForyStruct
struct Person {
    var name: String = ""
    var age: Int32 = 0
}

let fory = Fory()
try fory.register(Person.self, name: "example.Person")

let bytes = try fory.serialize(Person(name: "Alice", age: 30))
let person: Person = try fory.deserialize(bytes)
print(person.name)

Scala

import org.apache.fory.scala.ForyScala

case class Person(name: String, age: Int)

val fory = ForyScala.builder().withXlang(true).build()
fory.register(classOf[Person], "example.Person")

val bytes = fory.serialize(Person("Alice", 30))
val person = fory.deserialize(bytes).asInstanceOf[Person]
println(person.name)

Kotlin

import org.apache.fory.kotlin.ForyKotlin

data class Person(val name: String, val age: Int)

fun main() {
    val fory = ForyKotlin.builder().withXlang(true).build()
    fory.register(Person::class.java, "example.Person")

    val bytes = fory.serialize(Person("Alice", 30))
    val person = fory.deserialize(bytes) as Person
    println(person.name)
}

For shared/circular references, polymorphism, numeric IDs versus names, and type-mapping rules, see the cross-language guide and type mapping specification.

Native Serialization

Use native mode when the writer and reader are in the same language. It is optimized for each language's native type system and can cover language-specific types, object graphs, and framework-replacement cases that xlang mode keeps out of the portable wire format. The languages below expose an explicit xlang=false or native-mode setting; implementations without that switch stay on their documented default path.

Choose Java native mode for Java/JVM-only replacements of JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers payloads. Choose Python native mode when replacing pickle or cloudpickle for Python-only payloads.

Keep class/type registration enabled for untrusted input. See the language guides for language-specific security and compatibility settings.

Java

Fory fory = Fory.builder()
    .withXlang(false)
    .requireClassRegistration(true)
    .build();
// Register, serialize, and deserialize as in the xlang example above.

Python

fory = pyfory.Fory(xlang=False, ref=True)
# Register, serialize, and deserialize as in the xlang example above.

Go

f := fory.New(fory.WithXlang(false))
// Register, serialize, and deserialize as in the xlang example above.

Rust

let mut fory = Fory::builder().xlang(false).build();
// Register, serialize, and deserialize as in the xlang example above.

C++

auto fory = Fory::builder().xlang(false).build();
// Register, serialize, and deserialize as in the xlang example above.

Scala

import org.apache.fory.scala.ForyScala

val fory = ForyScala.builder()
  .withXlang(false)
  .requireClassRegistration(true)
  .build()
// Register, serialize, and deserialize as in the xlang example above.

Kotlin

import org.apache.fory.kotlin.ForyKotlin

val fory = ForyKotlin.builder()
    .withXlang(false)
    .requireClassRegistration(true)
    .build()
// Register, serialize, and deserialize as in the xlang example above.

Schema IDL

Fory IDL is Fory's schema language for shared data models. It supports references, nullable fields, lists, maps, arrays, enums, messages, and unions, and generates native data structures for Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Use it when multiple languages need one shared contract.

package tree;

message TreeNode {
    string id = 1;
    string name = 2;

    list<ref TreeNode> children = 3;
    ref(weak=true) TreeNode parent = 4; // back-pointer
}

See the Fory IDL and compiler guide.

Row Format

Row format is for random access and partial reads. These examples encode an object with an integer array field, then read one array element from the binary row without rebuilding the object.

Python

from dataclasses import dataclass
from typing import List

import pyfory

@dataclass
class User:
    id: pyfory.Int32
    name: str
    scores: List[pyfory.Int32]

encoder = pyfory.encoder(User)
binary = encoder.to_row(User(1, "Alice", [98, 100, 95])).to_bytes()

row = pyfory.RowData(encoder.schema, binary)
print(row.name)
print(row.scores[1])

Java

public class User {
  public int id;
  public String name;
  public int[] scores;
}

RowEncoder<User> encoder = Encoders.bean(User.class);

User user = new User();
user.id = 1;
user.name = "Alice";
user.scores = new int[] {98, 100, 95};

BinaryRow row = encoder.toRow(user);

Schema schema = encoder.schema();
Schema.StringField nameField = schema.stringField("name");
Schema.ArrayField scoresField = schema.arrayField("scores");

String name = nameField.get(row);
ArrayData scores = scoresField.get(row);
int secondScore = scores.getInt32(1);

For Java imports, nested structs, arrays/maps, Arrow integration, and partial deserialization, see the Java row-format guide, the Python row-format guide, and the row-format specification.

Documentation

User Guides

Guide Source Website
Java docs/guide/java View
Python docs/guide/python View
Rust docs/guide/rust View
C++ docs/guide/cpp View
Go docs/guide/go View
JavaScript/TypeScript docs/guide/javascript View
C# docs/guide/csharp View
Swift docs/guide/swift View
Dart docs/guide/dart View
Scala docs/guide/scala View
Kotlin docs/guide/kotlin View
Cross-language xlang docs/guide/xlang View
Schema IDL/compiler docs/compiler View
GraalVM native image docs/guide/java/graalvm-support.md View
Android docs/guide/kotlin/android-support.md View
Development docs/DEVELOPMENT.md View

Specifications

Specification Source Website
Xlang serialization xlang_serialization_spec.md View
Java serialization java_serialization_spec.md View
Row format row_format_spec.md View
Cross-language mapping xlang_type_mapping.md View

Community

Contributing

Read CONTRIBUTING.md and docs/DEVELOPMENT.md before sending pull requests. Bug reports, docs fixes, tests, benchmarks, and implementation improvements are welcome.

License

Apache Fory™ is licensed under the Apache License 2.0.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Sun Jul 12 2026 08:57:38 GMT-0900 (Hawaii-Aleutian Daylight Time)