Skip to main content
Version: 0.17

Cross-Language Serialization

Apache Fory™ Dart serializes to the same binary format as the Java, Go, C#, Python, Rust, and Swift Fory runtimes. You can write a message in Dart and read it in Java — or any other direction — without any conversion layer.

Setup

Create a Fory instance as normal. There is no separate "cross-language mode" to enable in Dart:

final fory = Fory(); // or Fory(compatible: true) for schema evolution

The key requirement is that both sides register the same type using the same identity.

Registration Identity

The most important rule: use the same type identity on every side. You have two options:

Numeric ID

Simpler for small, tightly-coordinated teams:

// Dart
ModelsFory.register(fory, Person, id: 100);

Namespace + Type Name

Better when multiple teams define types independently:

// Dart
ModelsFory.register(
fory,
Person,
namespace: 'example',
typeName: 'Person',
);

Do not mix the two strategies for the same type across runtimes.

Dart to Java Example

Dart

import 'package:fory/fory.dart';

part 'person.fory.dart';

@ForyStruct()
class Person {
Person();

String name = '';
Int32 age = Int32(0);
}

final fory = Fory();
PersonFory.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = Int32(30));

Java

Fory fory = Fory.builder()
.withLanguage(Language.XLANG)
.build();

fory.register(Person.class, 100);
Person value = (Person) fory.deserialize(bytesFromDart);

Dart to C# Example

Dart

final fory = Fory(compatible: true);
PersonFory.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = Int32(30));

CSharp

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

Fory fory = Fory.Builder()
.Compatible(true)
.Build();

fory.Register<Person>(100);
Person person = fory.Deserialize<Person>(payloadFromDart);

Dart to Go Example

Dart

final fory = Fory();
PersonFory.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = Int32(30));

Go

type Person struct {
Name string
Age int32
}

f := fory.New(fory.WithXlang(true))
_ = f.RegisterStruct(Person{}, 100)

var person Person
_ = f.Deserialize(bytesFromDart, &person)

Field Matching Rules

Fory matches fields by name or by stable field ID. For robust cross-language interop:

  1. Use the same type identity on every side (same numeric ID or same namespace + typeName).
  2. Assign stable @ForyField(id: ...) values to all fields before shipping the first payload.
  3. Keep field names consistent or rely on IDs, since Dart typically uses lowerCamelCase while Go uses PascalCase for exported fields and C# often uses PascalCase properties.
  4. Use compatible numeric types: Int32 in Dart for Java int, Go int32, and C# int; double in Dart for 64-bit floats; Float32 for 32-bit.
  5. Use Timestamp and LocalDate for date/time fields rather than raw DateTime.
  6. Validate real round trips across all languages before shipping.

Type Mapping Notes for Dart

Because Dart int is not itself a promise about the exact xlang wire width, prefer wrappers or numeric field annotations when exact cross-language interpretation matters:

  • Int32 for xlang int32
  • UInt32 for xlang uint32
  • Float16 and Float32 for reduced-width floating point
  • Timestamp and LocalDate for explicit temporal semantics

See Supported Types and xlang type mapping.

Validation

Before relying on a cross-language contract in production, test a payload end-to-end through every runtime you support.

Run the Dart side:

dart run build_runner build --delete-conflicting-outputs
dart analyze
dart test