xlang-serialization.md

June 29, 2026 · View on GitHub

This page explains how to use Fory xlang serialization between C++ and other languages.

Overview

Apache Fory™ enables seamless data exchange between C++, Java, Python, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Xlang mode ensures binary compatibility across all supported languages.

Create an Xlang Fory Instance

C++ defaults to xlang mode. Compatible schema evolution is also the xlang default. Set the mode explicitly in xlang examples:

#include "fory/serialization/fory.h"

using namespace fory::serialization;

auto fory = Fory::builder().xlang(true).build();

Xlang Example

C++ Producer

#include "fory/serialization/fory.h"
#include <fstream>

using namespace fory::serialization;

struct Message {
  std::string topic;
  int64_t timestamp;
  std::map<std::string, std::string> headers;
  std::vector<uint8_t> payload;

  bool operator==(const Message &other) const {
    return topic == other.topic && timestamp == other.timestamp &&
           headers == other.headers && payload == other.payload;
  }
};
FORY_STRUCT(Message, topic, timestamp, headers, payload);

int main() {
  auto fory = Fory::builder().xlang(true).build();
  fory.register_struct<Message>(100);

  Message msg{
      "events.user",
      1699999999000,
      {{"content-type", "application/json"}},
      {'h', 'e', 'l', 'l', 'o'}
  };

  auto result = fory.serialize(msg);
  if (result.ok()) {
    auto bytes = std::move(result).value();
    // write to file, send over network, etc.
    std::ofstream file("message.bin", std::ios::binary);
    file.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
  }
  return 0;
}

Java Consumer

import org.apache.fory.Fory;

public class Message {
    public String topic;
    public long timestamp;
    public Map<String, String> headers;
    public byte[] payload;
}

public class Consumer {
    public static void main(String[] args) throws Exception {
        Fory fory = Fory.builder()
            .withXlang(true)
            .build();
        fory.register(Message.class, 100);  // Same ID as C++

        byte[] bytes = Files.readAllBytes(Path.of("message.bin"));
        Message msg = (Message) fory.deserialize(bytes);

        System.out.println("Topic: " + msg.topic);
        System.out.println("Timestamp: " + msg.timestamp);
    }
}

Python Consumer

import pyfory

class Message:
    topic: str
    timestamp: int
    headers: dict[str, str]
    payload: bytes

fory = pyfory.Fory(xlang=True)
fory.register(Message, type_id=100)  # Same ID as C++

with open("message.bin", "rb") as f:
    data = f.read()

msg = fory.deserialize(data)
print(f"Topic: {msg.topic}")
print(f"Timestamp: {msg.timestamp}")

Type Mapping

Primitive Types

C++ TypeJava TypePython TypeGo TypeRust Type
boolbooleanboolboolbool
int8_tbyteintint8i8
int16_tshortintint16i16
int32_tintintint32i32
int64_tlongintint64i64
floatfloatfloatfloat32f32
doubledoublefloatfloat64f64
fory::float16_tFloat16pyfory.Float16float16.Float16Float16
fory::bfloat16_tBFloat16pyfory.BFloat16bfloat16.BFloat16BFloat16

String Types

C++ TypeJava TypePython TypeGo TypeRust Type
std::stringStringstrstringString

Collection Types

C++ TypeJava TypePython TypeGo TypeRust Type
std::vector<T>List<T>list[]TVec<T>
std::vector<fory::float16_t>Float16ListFloat16Array[]float16.Float16Vec<Float16>
std::vector<fory::bfloat16_t>BFloat16ListBFloat16Array[]bfloat16.BFloat16Vec<BFloat16>
std::set<T>Set<T>setmap[T]struct{}HashSet<T>
std::map<K,V> / std::unordered_map<K,V>Map<K,V>dictmap[K]VHashMap<K,V>

Lists and Dense Arrays

std::vector<T> maps to Fory list<T> by default in handwritten C++ structs. Use the field metadata DSL's array node when the schema is dense array<T>.

Fory schemaC++ metadata sketch
list<int32>fory::F(id).list(fory::T::int32())
array<bool>fory::F(id).array(fory::T::bool_())
array<int8>fory::F(id).array(fory::T::int8())
array<int16>fory::F(id).array(fory::T::int16())
array<int32>fory::F(id).array(fory::T::int32())
array<int64>fory::F(id).array(fory::T::int64())
array<uint8>fory::F(id).array(fory::T::uint8())
array<uint16>fory::F(id).array(fory::T::uint16())
array<uint32>fory::F(id).array(fory::T::uint32())
array<uint64>fory::F(id).array(fory::T::uint64())
array<float16>fory::F(id).array(fory::T::float16())
array<bfloat16>fory::F(id).array(fory::T::bfloat16())
array<float32>fory::F(id).array(fory::T::float32())
array<float64>fory::F(id).array(fory::T::float64())

Temporal Types

C++ TypeJava TypePython TypeGo Type
fory::TimestampInstantdatetimetime.Time
fory::DurationDurationtimedeltatime.Duration
fory::DateLocalDatedatetime.datetime.Time

Field Order Requirements

Critical: Fields are sorted by snake_case field name. The converted names must match across languages.

C++

struct Person {
  std::string name;   // Field 0
  int32_t age;        // Field 1
  std::string email;  // Field 2
};
FORY_STRUCT(Person, name, age, email);  // Order matters!

Java

public class Person {
    public String name;   // Field 0
    public int age;       // Field 1
    public String email;  // Field 2
}

Python

class Person:
    name: str    # Field 0
    age: int     # Field 1
    email: str   # Field 2

Type ID Consistency

All languages must use the same type IDs:

// C++
fory.register_struct<Person>(100);
fory.register_struct<Address>(101);
fory.register_struct<Order>(102);
// Java
fory.register(Person.class, 100);
fory.register(Address.class, 101);
fory.register(Order.class, 102);
# Python
fory.register(Person, type_id=100)
fory.register(Address, type_id=101)
fory.register(Order, type_id=102)

Compatible Mode

Xlang mode already uses compatible schema evolution by default. Keep that default for schemas that may evolve independently:

auto fory = Fory::builder().xlang(true).build();

Compatible mode allows:

  • Adding new fields (with defaults)
  • Removing unused fields
  • Reordering fields

Troubleshooting

Type Mismatch Errors

Error: Type mismatch: expected 100, got 101

Solution: Ensure type IDs match across all languages.

Encoding Errors

Error: Invalid UTF-8 sequence

Solution: Ensure strings are valid UTF-8 in all languages.