title: Xlang Serialization sidebar_position: 2 id: xlang_serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

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.

Related Topics