Services
gRPC is serialization-agnostic by design, and kotlinx.rpc follows this principle. Protobuf is the most common choice, but you can use any serialization format by providing a custom GrpcMarshaller. kotlinx.serialization support is provided out of the box.
There are three ways to define a gRPC service, depending on how much code generation you need:
Full proto generation — service and messages defined in .proto files
Partial proto generation — only messages from .proto, service interface written by hand
No proto — everything hand-written, any serialization via marshallers
Full proto generation
This is the default path. You define both messages and the service in a .proto file, and the Gradle plugin generates all Kotlin code. You only need to implement the service interface.
Given the following schema:
The plugin generates the Image and RecogniseResult message types and the ImageRecognizer service interface annotated with @Grpc. You implement the service:
Register it on the server and call it from the client:
For details on what the protoc plugins and the compiler plugin generate, see Schema and codegen.
Partial proto generation
In this mode you only define messages in a .proto file (no service block) and write the @Grpc service interface yourself. This gives you full control over the service contract while still using generated Protobuf types.
Given the following schema:
Write the service interface by hand with the @Grpc annotation:
The generated Image and RecogniseResult types already have built-in Protobuf marshallers, so no additional marshaller configuration is needed. The rest of the setup — implementation, server, and client — is identical to full proto generation.
No proto
You can skip .proto files entirely and write both the service interface and message types in Kotlin. In this case you must provide a GrpcMarshallerResolver so the framework knows how to serialize and deserialize your types over the wire.
Using kotlinx.serialization
The grpc-marshaller-kotlinx-serialization module provides a ready-made resolver that works with any kotlinx.serialization format. Add the dependency:
Define your types with @Serializable and your service with @Grpc:
Pass the resolver when creating the server and client using asMarshallerResolver():
Any StringFormat (such as Json) or BinaryFormat (such as Cbor or ProtoBuf) can be used.
Custom marshallers
For full control over serialization, implement GrpcMarshaller directly and provide a GrpcMarshallerResolver:
Pass the resolver to both server and client via messageMarshallerResolver, as shown above.
Alternatively, you can annotate individual message types with @WithGrpcMarshaller to bind a marshaller directly to a type. In this case no resolver is needed for that type: