Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
378 changes: 378 additions & 0 deletions blog/2026-06-16-fory_1_2_0_release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
---
slug: fory_1_2_0_release
title: Fory v1.2.0 Released
authors: [chaokunyang]
tags: [fory, java, kotlin, scala, android, python, rust, c++, go, c#, swift, dart, compiler]
---

The Apache Fory team is pleased to announce the 1.2.0 release. This release includes [38 PRs](https://github.com/apache/fory/compare/v1.1.0...v1.2.0) from 9 distinct contributors and continues to improve the cross-language runtime across supported languages. See the [Install](https://fory.apache.org/docs/start/install) page to get the libraries for your platform.

## 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 implementation for the core runtime so applications
can run on JDK 25+ without resolving `sun.misc.Unsafe` from Fory's active class
graph.

The implementation keeps the fast JDK 8-24 paths for older runtimes, but JDK 25+
loads replacement classes from the versioned jar area. Those replacements move
field access and memory-buffer primitives to supported JVM mechanisms such as
`VarHandle`, `MethodHandle`, arrays, and `ByteBuffer`. Constructor-bypassing
allocation is also handled explicitly: on JDK 25+ classes that previously
depended on Unsafe allocation should provide an accessible no-arg constructor,
use record construction, or register a custom serializer.

This is an important compatibility milestone for Java users because the runtime
no longer depends on a terminally deprecated Unsafe API path when the application
runs on Java 25 and later.

## 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, Python, Java, and C++, but 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. You can still make the setting explicit:

```java
Fory fory = Fory.builder()
.withXlang(true)
.withCompatible(true)
.build();
```

```python
import pyfory

fory = pyfory.Fory(xlang=True, compatible=True)
native_fory = pyfory.Fory(xlang=False, compatible=True)
```

Rust example:

```rust
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");
```

Python example:

```python
from dataclasses import dataclass
import pyfory

@dataclass
class MetricV1:
value: pyfory.Int64

@dataclass
class MetricV2:
value: str

writer = pyfory.Fory(xlang=True, compatible=True)
writer.register(MetricV1, name="example.Metric")

reader = pyfory.Fory(xlang=True, compatible=True)
reader.register(MetricV2, name="example.Metric")

data = writer.dumps(MetricV1(42))
assert reader.loads(data).value == "42"
```

Java example:

```java
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");
```

C++ example:

```cpp
#include <cassert>
#include <string>
#include "fory/serialization/fory.h"

using namespace fory::serialization;

struct MetricV1 {
int64_t value;
};
FORY_STRUCT(MetricV1, value);

struct MetricV2 {
std::string value;
};
FORY_STRUCT(MetricV2, value);

auto writer = Fory::builder().xlang(true).compatible(true).build();
auto reader = Fory::builder().xlang(true).compatible(true).build();

writer.register_struct<MetricV1>("example", "Metric");
reader.register_struct<MetricV2>("example", "Metric");

auto bytes = writer.serialize(MetricV1{42}).value();
auto value = reader.deserialize<MetricV2>(bytes).value();
assert(value.value == "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.

The examples below use this shared schema:

```protobuf
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:

```bash
foryc service.fdl --rust_out=./generated/rust --grpc
```

```rust
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?;
```

Go generation emits grpc-go interfaces and a Fory-backed `CodecV2`:

```bash
foryc service.fdl --go_out=./generated/go --grpc
```

```go
server := grpc.NewServer(
grpc.ForceServerCodecV2(greeter.CodecV2{}),
)
greeter.RegisterGreeterServer(server, greeterService{})

client := greeter.NewGreeterClient(conn)
reply, err := client.SayHello(ctx, &greeter.HelloRequest{Name: "Fory"})
```

JavaScript generation supports both Node.js gRPC and browser gRPC-Web:

```bash
foryc service.fdl --javascript_out=./generated/javascript --grpc --grpc-web
```

```ts
import * as grpc from "@grpc/grpc-js";
import { addGreeterService, createGreeterClient } from "./service_grpc";
import { createGreeterWebPromiseClient } from "./service_grpc_web";

addGreeterService(server, greeterHandlers);

const nodeClient = createGreeterClient(
"localhost:50051",
grpc.credentials.createInsecure(),
);
nodeClient.sayHello({ name: "Fory" }, callback);

const webClient = createGreeterWebPromiseClient("https://api.example.com");
const reply = await webClient.sayHello({ name: "Fory" });
```

Browser gRPC-Web follows gRPC-Web transport limits: unary and server-streaming
methods are supported, while client-streaming and bidirectional streaming remain
Node.js/native gRPC shapes.

Kotlin generation emits grpc-kotlin coroutine companions:

```bash
foryc service.fdl --kotlin_out=./generated/kotlin --grpc
```

```kotlin
class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
override suspend fun sayHello(request: HelloRequest): HelloReply =
HelloReply(reply = "Hello, ${request.name}")
}

val server = ServerBuilder
.forPort(50051)
.addService(GreeterService())
.build()
.start()

val stub = GreeterGrpcKt.GreeterCoroutineStub(channel)
val reply = stub.sayHello(HelloRequest(name = "Fory"))
```

Scala generation emits Scala 3 grpc-java companions with Scala-friendly client
handles:

```bash
foryc service.fdl --scala_out=./generated/scala --grpc
```

```scala
final class GreeterService extends GreeterGrpc.GreeterImplBase {
override def sayHello(request: HelloRequest): HelloReply =
HelloReply(s"Hello, ${request.name}")
}

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

val client = GreeterGrpc.newClient(channel)
val reply = Await.result(client.sayHello(HelloRequest("Fory")).asFuture, 30.seconds)
```

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 https://github.com/apache/fory/pull/3721
* refactor(format): inline custom-codec dispatch in row codecs by @stevenschlansker in https://github.com/apache/fory/pull/3716
* perf(format): cache compact row layout per nested slot by @stevenschlansker in https://github.com/apache/fory/pull/3717
* feat(java): remove sun.misc.Unsafe for jdk25 by @chaokunyang in https://github.com/apache/fory/pull/3702
* feat(rust): support thread safe `Arc<dyn Any + Send + Sync>` type by @chaokunyang in https://github.com/apache/fory/pull/3736
* refactor(rust): refactor sync send type by @chaokunyang in https://github.com/apache/fory/pull/3737
* feat(xlang): refine register by name api by @chaokunyang in https://github.com/apache/fory/pull/3739
* feat(xlang): support compatible scalar read conversions by @chaokunyang in https://github.com/apache/fory/pull/3740
* feat: default compatible mode for native serialization by @chaokunyang in https://github.com/apache/fory/pull/3742
* perf: optimize compatible mode read performance by @chaokunyang in https://github.com/apache/fory/pull/3743
* feat(compiler): handle Rust identifier escaping and name collisions by @BaldDemian in https://github.com/apache/fory/pull/3744
* feat(go): implement grpc stub generation by @ayush00git in https://github.com/apache/fory/pull/3698
* refactor(compiler): generate C++ unordered map for Fory map by @BaldDemian in https://github.com/apache/fory/pull/3745
* feat(compiler): handle nested container ref pointer options in C++ compiler correctly by @BaldDemian in https://github.com/apache/fory/pull/3735
* feat: add more read checks by @chaokunyang in https://github.com/apache/fory/pull/3748
* feat(compiler): support Rust gRPC code generation by @BaldDemian in https://github.com/apache/fory/pull/3738
* feat(cpp): support struct property accessors by @chaokunyang in https://github.com/apache/fory/pull/3751
* feat(python): make scalar wire markers typing-friendly by @chaokunyang in https://github.com/apache/fory/pull/3756
* feat(kotlin): add kotlin grpc support by @chaokunyang in https://github.com/apache/fory/pull/3757
* feat(rust): make fory-derive generated code use exported api in fory rust lib by @chaokunyang in https://github.com/apache/fory/pull/3759
* feat(scala): add generated grpc service support for scala by @chaokunyang in https://github.com/apache/fory/pull/3762
* feat(csharp): add generated grpc support for C# by @chaokunyang in https://github.com/apache/fory/pull/3761
* feat(javascript): add javascript gRPC support for nodejs/browser by @chaokunyang in https://github.com/apache/fory/pull/3760

## Bug Fix

* fix(go): return nil serializer on getTypeInfo err by @ayush00git in https://github.com/apache/fory/pull/3719
* fix(benchmarks): uses outdated google-java-format, upgrade spotless by @stevenschlansker in https://github.com/apache/fory/pull/3722
* fix(format): pass row body size, not full payload size, to BinaryRow.pointTo by @stevenschlansker in https://github.com/apache/fory/pull/3715
* fix(java): fix deflater memory leak by @MNTMDEV in https://github.com/apache/fory/pull/3726
* fix(compiler): handle nested container ref pointer options in Rust compiler correctly by @BaldDemian in https://github.com/apache/fory/pull/3731
* fix(java): ignore non-Scala/Lombok-style default helper methods by @mandrean in https://github.com/apache/fory/pull/3733
* fix(c++): std::unordered_map cannot be used in struct. (#3727) by @ruoruoniao in https://github.com/apache/fory/pull/3728
* fix(grpc): fix rust/go grpc support by @chaokunyang in https://github.com/apache/fory/pull/3753
* fix(cpp): align unsigned struct default encoding by @chaokunyang in https://github.com/apache/fory/pull/3754

## Other Improvements

* chore(deps): fix vulnerable dependencies by @chaokunyang in https://github.com/apache/fory/pull/3741
* chore: Bump MessagePack from 2.5.187 to 2.5.301 by @dependabot[bot] in https://github.com/apache/fory/pull/3750
* chore(deps): bump Go gRPC test dependencies by @chaokunyang in https://github.com/apache/fory/pull/3763

## New Contributors

* @MNTMDEV made their first contribution in https://github.com/apache/fory/pull/3726
* @ruoruoniao made their first contribution in https://github.com/apache/fory/pull/3728

**Full Changelog**: https://github.com/apache/fory/compare/v1.1.0...v1.2.0
4 changes: 2 additions & 2 deletions docs/compiler/compiler-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ Add the Fory dependency to `pubspec.yaml`:

```yaml
dependencies:
fory: ^1.1.0
fory: ^1.2.0

dev_dependencies:
build_runner: ^2.4.0
Expand Down Expand Up @@ -899,5 +899,5 @@ fory = "x.y.z"

```yaml
dependencies:
fory: ^1.1.0
fory: ^1.2.0
```
Loading
Loading