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.Unsafeusage 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
- feat(java): add java9/16 module-info support by @chaokunyang in #3721
- refactor(format): inline custom-codec dispatch in row codecs by @stevenschlansker in #3716
- perf(format): cache compact row layout per nested slot by @stevenschlansker in #3717
- feat(java): remove sun.misc.Unsafe for jdk25 by @chaokunyang in #3702
- feat(rust): support thread safe
Arc<dyn Any + Send + Sync>type by @chaokunyang in #3736 - refactor(rust): refactor sync send type by @chaokunyang in #3737
- feat(xlang): refine register by name api by @chaokunyang in #3739
- feat(xlang): support compatible scalar read conversions by @chaokunyang in #3740
- feat: default compatible mode for native serialization by @chaokunyang in #3742
- perf: optimize compatible mode read performance by @chaokunyang in #3743
- feat(compiler): handle Rust identifier escaping and name collisions by @BaldDemian in #3744
- feat(go): implement grpc stub generation by @ayush00git in #3698
- refactor(compiler): generate C++ unordered map for Fory map by @BaldDemian in #3745
- feat(compiler): handle nested container ref pointer options in C++ compiler correctly by @BaldDemian in #3735
- feat: add more read checks by @chaokunyang in #3748
- feat(compiler): support Rust gRPC code generation by @BaldDemian in #3738
- feat(cpp): support struct property accessors by @chaokunyang in #3751
- feat(python): make scalar wire markers typing-friendly by @chaokunyang in #3756
- feat(kotlin): add kotlin grpc support by @chaokunyang in #3757
- feat(rust): make fory-derive generated code use exported api in fory rust lib by @chaokunyang in #3759
- feat(scala): add generated grpc service support for scala by @chaokunyang in #3762
- feat(csharp): add generated grpc support for C# by @chaokunyang in #3761
- feat(javascript): add javascript gRPC support for nodejs/browser by @chaokunyang in #3760
Bug Fix
- fix(go): return nil serializer on getTypeInfo err by @ayush00git in #3719
- fix(benchmarks): uses outdated google-java-format, upgrade spotless by @stevenschlansker in #3722
- fix(format): pass row body size, not full payload size, to BinaryRow.pointTo by @stevenschlansker in #3715
- fix(java): fix deflater memory leak by @MNTMDEV in #3726
- fix(compiler): handle nested container ref pointer options in Rust compiler correctly by @BaldDemian in #3731
- fix(java): ignore non-Scala/Lombok-style default helper methods by @mandrean in #3733
- fix(c++): std::unordered_map cannot be used in struct. (#3727) by @ruoruoniao in #3728
- fix(grpc): fix rust/go grpc support by @chaokunyang in #3753
- fix(cpp): align unsigned struct default encoding by @chaokunyang in #3754
Other Improvements
- chore(deps): fix vulnerable dependencies by @chaokunyang in #3741
- chore: Bump MessagePack from 2.5.187 to 2.5.301 by @dependabot[bot] in #3750
- chore(deps): bump Go gRPC test dependencies by @chaokunyang in #3763
New Contributors
- @MNTMDEV made their first contribution in #3726
- @ruoruoniao made their first contribution in #3728
Full Changelog: v1.1.0...v1.2.0