kotlinx.rpc 0.10.2 Help

Using generated code

This page shows how to work with the Kotlin types generated from .proto schemas. For details on what the protoc plugins and the compiler plugin produce, see Schema and codegen.

Building messages

Every generated message has a builder DSL accessed through the Companion.invoke operator:

val request = HelloRequest { name = "kotlinx.rpc" }

Copying messages

Messages are immutable. Use copy to create a modified version:

val updated = request.copy { name = "grpc" }

Enums

Proto enums become Kotlin sealed types. Unknown values received over the wire are represented as UNRECOGNIZED:

enum Priority { PRIORITY_UNSPECIFIED = 0; HIGH = 1; LOW = 2; } message Task { Priority priority = 1; }
val task = Task { priority = Priority.HIGH }

Repeated fields

Repeated fields map to Kotlin List:

message Numbers { repeated int32 values = 1; repeated string labels = 2; }
val numbers = Numbers { values = listOf(1, 2, 3) labels = listOf("a", "b", "c") } val extended = numbers.copy { values = values + 4 }

Map fields

Map fields map to Kotlin Map:

message Config { map<string, int64> settings = 1; }
val config = Config { settings = mapOf("timeout" to 30L, "retries" to 3L) } val updated = config.copy { settings = settings + ("retries" to 5L) }

Oneof fields

Oneof fields become Kotlin sealed interfaces. Each case is a data class wrapping the value:

message Event { oneof payload { string text = 1; int32 code = 2; } }
val event = Event { payload = Event.Payload.Text("hello") } when (event.payload) { is Event.Payload.Text -> println(event.payload.value) is Event.Payload.Code -> println(event.payload.value) null -> {} }

Nested messages

Nested message types are accessed through the parent type:

message Outer { message Inner { int32 value = 1; } Inner inner = 1; }
val outer = Outer { inner = Outer.Inner { value = 42 } }

Streaming RPCs

Streaming signatures use Flow. Given the following schema:

service Echo { rpc ServerStream(EchoRequest) returns (stream EchoResponse); rpc ClientStream(stream EchoRequest) returns (EchoResponse); rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); }

The generated interface looks like this:

@Grpc interface Echo { fun ServerStream(message: EchoRequest): Flow<EchoResponse> suspend fun ClientStream(message: Flow<EchoRequest>): EchoResponse fun BidiStream(message: Flow<EchoRequest>): Flow<EchoResponse> }

Implementation:

class EchoImpl : Echo { override fun ServerStream(message: EchoRequest): Flow<EchoResponse> { return flow { repeat(3) { emit(EchoResponse { text = message.text }) } } } override suspend fun ClientStream(message: Flow<EchoRequest>): EchoResponse { return EchoResponse { text = message.last().text } } override fun BidiStream(message: Flow<EchoRequest>): Flow<EchoResponse> { return message.map { EchoResponse { text = it.text } } } }

Client usage:

val echo = client.withService<Echo>() // Server streaming echo.ServerStream(EchoRequest { text = "hello" }).collect { println(it.text) } // Client streaming val result = echo.ClientStream(flow { emit(EchoRequest { text = "one" }) emit(EchoRequest { text = "two" }) }) // Bidirectional streaming echo.BidiStream(flow { emit(EchoRequest { text = "ping" }) }).collect { println(it.text) }
Last modified: 10 March 2026