Skip to main content
Version: 1.2.0

Kotlin gRPC Support

Fory IDL can generate Kotlin coroutine gRPC companions. The generated gRPC files use normal grpc-java and grpc-kotlin APIs, while each request and response message is serialized with Fory.

Use this mode when both RPC peers are generated from the same Fory IDL, protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use normal protobuf gRPC generation for APIs that must be consumed by generic protobuf clients, reflection tools, or components that expect protobuf message bytes.

Add Dependencies

Add Fory Kotlin, KSP, grpc-java, grpc-kotlin, coroutines, and one grpc-java transport to the application or service module that compiles the generated source.

plugins {
id("com.google.devtools.ksp") version "<ksp-version>"
}

dependencies {
implementation("org.apache.fory:fory-kotlin:<fory-version>")
ksp("org.apache.fory:fory-kotlin-ksp:<fory-version>")

implementation("io.grpc:grpc-api:<grpc-version>")
implementation("io.grpc:grpc-stub:<grpc-version>")
implementation("io.grpc:grpc-kotlin-stub:<grpc-kotlin-version>")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:<coroutines-version>")

runtimeOnly("io.grpc:grpc-netty-shaded:<grpc-version>")
}

Use a different grpc-java transport if your application already standardizes on one. Generated Kotlin Fory gRPC does not require grpc-protobuf for payload encoding.

Define a Service

Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers rpc_service definitions. A Fory IDL service looks like this:

package demo.greeter;

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

Generate Kotlin model and gRPC companion code with --grpc:

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

For this schema, the Kotlin generator emits:

FilePurpose
HelloRequest.ktFory model type for the request
HelloReply.ktFory model type for the response
ServiceForyModuleFory registration module for generated types
GreeterGrpcKt.ktCoroutine service base, stubs, and codecs

Run KSP when compiling the generated model files so the schema serializers are available at runtime. Generated request and response types are registered by the generated schema module used by the service companion, so service implementations do not perform manual serializer registration.

Implement a Server

Implement the generated coroutine base class and register it with a normal grpc-java server.

import demo.greeter.GreeterGrpcKt
import demo.greeter.HelloReply
import demo.greeter.HelloRequest
import io.grpc.ServerBuilder

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()

Unimplemented generated methods fail with gRPC UNIMPLEMENTED. Exceptions thrown by your service method follow grpc-kotlin server behavior.

Create a Client

Construct the generated coroutine stub directly from a grpc-java channel.

import demo.greeter.GreeterGrpcKt
import demo.greeter.HelloRequest
import io.grpc.ManagedChannelBuilder

val channel = ManagedChannelBuilder
.forAddress("localhost", 50051)
.usePlaintext()
.build()

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

Channel construction, shutdown, deadlines, credentials, interceptors, load balancing, retries, and server lifecycle stay normal grpc-java/grpc-kotlin responsibilities.

Streaming RPCs

Fory service definitions can use the same gRPC streaming shapes:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

Streaming RPCs use kotlinx.coroutines.flow.Flow.

IDL shapeServer methodClient method
rpc A (Req) returns (Res)suspend fun a(request: Req): Ressuspend fun a(request: Req): Res
rpc A (Req) returns (stream Res)fun a(request: Req): Flow<Res>fun a(request: Req): Flow<Res>
rpc A (stream Req) returns (Res)suspend fun a(requests: Flow<Req>): Ressuspend fun a(requests: Flow<Req>): Res
rpc A (stream Req) returns (stream Res)fun a(requests: Flow<Req>): Flow<Res>fun a(requests: Flow<Req>): Flow<Res>

The generated method path keeps the exact service and method names from the schema, for example /demo.greeter.Greeter/SayHello.

Server implementations can return or consume Flow values directly:

import demo.greeter.GreeterGrpcKt
import demo.greeter.HelloReply
import demo.greeter.HelloRequest
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList

class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
override fun lotsOfReplies(request: HelloRequest): Flow<HelloReply> = flow {
emit(HelloReply(reply = "Hello, ${request.name}"))
emit(HelloReply(reply = "Welcome, ${request.name}"))
}

override suspend fun lotsOfGreetings(
requests: Flow<HelloRequest>
): HelloReply {
val names = requests.toList().joinToString(", ") { it.name }
return HelloReply(reply = names)
}

override fun chat(requests: Flow<HelloRequest>): Flow<HelloReply> =
requests.map { request ->
HelloReply(reply = "Hello, ${request.name}")
}
}

Generated clients expose the matching coroutine and Flow APIs:

import demo.greeter.HelloRequest
import kotlinx.coroutines.flow.flowOf

stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply ->
println(reply.reply)
}

val summary = stub.lotsOfGreetings(
flowOf(
HelloRequest(name = "Ada"),
HelloRequest(name = "Grace"),
)
)
println(summary.reply)

stub.chat(
flowOf(
HelloRequest(name = "Fory"),
HelloRequest(name = "RPC"),
)
).collect { reply ->
println(reply.reply)
}

Operations

The generated service code only replaces request and response serialization. All normal gRPC operational features still belong to grpc-java and grpc-kotlin:

  • Deadlines and cancellations
  • TLS and authentication
  • Name resolution and load balancing
  • Client and server interceptors
  • Status codes and metadata
  • Channel pooling and lifecycle management

Interoperability

Generated Kotlin service companions use Fory binary payloads inside gRPC frames. They are interoperable with other Fory gRPC companions generated from the same schema, such as Java, Go, Python, and Rust companions. Generic protobuf gRPC clients cannot decode these payloads.

Direct union request and response types are supported for Fory IDL services. For protobuf input, use the protobuf service shapes accepted by the protobuf frontend; protobuf oneof fields are translated into Fory union fields inside messages.

Troubleshooting

Generated service file is missing

Pass --grpc together with --kotlin_out. Schemas without service definitions only generate model files and the schema module.

Serializer class not found at runtime

Ensure KSP runs for the generated Kotlin model sources and that fory-kotlin-ksp uses the same Fory version as fory-kotlin.

gRPC classes are unresolved

Add grpc-java and grpc-kotlin dependencies to the application module. Fory Kotlin artifacts do not add those dependencies automatically.

A protobuf client cannot read responses

Fory gRPC uses Fory binary protocol payloads, not protobuf wire-format messages. Use generated Fory gRPC companions on both sides for the same service schema.