gRPC in Kotlin: when to use it so you don't regret it
Imagine: you are writing microservices in Kotlin, everything works, but requests between them become sluggish. The REST API you loved so much starts to annoy you — too much serialization, too much HTTP overhead. You start googling "fast way to communicate between microservices" and stumble upon gRPC. And immediately the question arises: "Isn't this overkill? Maybe leave it as is?"
Let's figure it out. gRPC in Kotlin is not just "another protocol". It is a completely different approach to inter-service communication. It is faster, stricter, and more powerful than REST, but also more complex. In this article, I will tell you when gRPC is your best friend and when it's better to stick with good old REST. And, of course, I will show real Kotlin code.
What is gRPC and why is it so popular in the Kotlin world?
gRPC is a remote procedure call (RPC) framework from Google. It uses Protocol Buffers (protobuf) for data serialization and HTTP/2 for transport. If REST is like sending letters by mail (each request is a separate letter), then gRPC is like a direct phone call: fast, bidirectional, with minimal latency.
For Kotlin developers, gRPC is especially interesting because:
- Kotlin coroutines pair perfectly with gRPC's asynchronous streams.
- Strict protobuf typing means fewer runtime errors.
- Support for bidirectional streaming — ideal for chats, games, and real-time applications.
But let's be honest: gRPC is not needed everywhere. And now I will show you how to determine if your project needs it.
When to use gRPC in Kotlin: 5 rock-solid scenarios
1. Microservices with high performance requirements
If your microservices communicate with tens of thousands of requests per second, gRPC outperforms REST by 2-5 times in speed. All thanks to the binary protobuf format and HTTP/2 multiplexing.
Example: you have a recommendation service in Kotlin that must return personalized content in milliseconds. REST with JSON will be "heavy" — each request contains field names, brackets, and other textual clutter. Protobuf transmits only numbers and strings without unnecessary wrapping.
// Definition in .proto fileservice Recommender { rpc GetRecommendations (UserRequest) returns (RecommendationsResponse);}
message UserRequest { string user_id = 1; int32 max_results = 2;}
message RecommendationsResponse { repeated Item items = 1;}The Kotlin client code will look concise:
val stub = RecommenderGrpc.newBlockingStub(channel)val request = UserRequest.newBuilder() .setUserId("user_123") .setMaxResults(10) .build()
val response = stub.getRecommendations(request)println("Received ${response.itemsCount} recommendations")2. Real-time data streaming
gRPC supports four types of streaming: server-side, client-side, bidirectional, and simple unary. In Kotlin, this is especially convenient with coroutines — you can stream data as a Flow.
Example: a log server that sends new records to the client in real time.
// Server-side in Kotlinclass LogServiceImpl : LogServiceGrpcKt.LogServiceCoroutineImplBase() { override fun streamLogs(request: LogRequest): Flow<LogEntry> = flow { val logSource = getLogSource(request.logType) logSource.collect { logEntry -> emit(logEntry) } }}
// Client-sideval stub = LogServiceGrpcKt.LogServiceCoroutineStub(channel)stub.streamLogs(LogRequest.newBuilder().setLogType("error").build()) .collect { log -> println("New log: ${log.message}") }Try doing the same with REST and WebSocket — it will be cumbersome and slow.