Quando APIs REST não são suficientes — seja por latência, volume de dados ou comunicação entre microsserviços — o gRPC é a alternativa que mais cresce no ecossistema backend. E a combinação com Kotlin e coroutines torna tudo ainda melhor: serviços assíncronos, streaming nativo com Flow e tipagem forte via Protocol Buffers.
Neste tutorial, vamos construir um serviço gRPC completo em Kotlin: definição do schema, servidor, cliente e streaming bidirecional.
O que é gRPC?
O gRPC é um framework de chamada remota de procedimentos (RPC) criado pelo Google. Diferente de REST, que usa JSON sobre HTTP/1.1, o gRPC usa Protocol Buffers (protobuf) sobre HTTP/2, trazendo:
- Serialização binária — até 10x mais rápida que JSON
- Streaming — unidirecional e bidirecional nativamente
- Geração de código — cliente e servidor gerados a partir de um arquivo
.proto - Multiplexing — múltiplas requisições sobre uma única conexão TCP
- Compressão nativa — payloads menores automaticamente
Para quem já trabalha com APIs REST em Kotlin, o gRPC é o próximo passo para cenários de alta performance.
Configurando o projeto
Crie um projeto Kotlin com Gradle e adicione as dependências do gRPC:
// build.gradle.kts
import com.google.protobuf.gradle.*
plugins {
kotlin("jvm") version "2.3.20"
id("com.google.protobuf") version "0.9.4"
application
}
repositories {
mavenCentral()
}
dependencies {
// gRPC Kotlin
implementation("io.grpc:grpc-kotlin-stub:1.4.3")
implementation("io.grpc:grpc-protobuf:1.70.0")
implementation("io.grpc:grpc-netty-shaded:1.70.0")
// Protobuf
implementation("com.google.protobuf:protobuf-kotlin:4.29.3")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
// Logging
implementation("ch.qos.logback:logback-classic:1.5.15")
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.29.3"
}
plugins {
create("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:1.70.0"
}
create("grpckt") {
artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.3:jdk8@jar"
}
}
generateProtoTasks {
all().forEach {
it.plugins {
create("grpc")
create("grpckt")
}
it.builtins {
create("kotlin")
}
}
}
}
application {
mainClass.set("com.exemplo.MainKt")
}
Se você prefere organizar versões de forma centralizada, confira nosso artigo sobre Gradle Version Catalog em Kotlin — funciona perfeitamente com projetos gRPC.
Definindo o serviço com Protocol Buffers
Crie o arquivo .proto que define a interface do serviço:
// src/main/proto/produto.proto
syntax = "proto3";
package com.exemplo;
option java_multiple_files = true;
option java_package = "com.exemplo.grpc";
// Mensagens
message ProdutoRequest {
string id = 1;
}
message ProdutoResponse {
string id = 1;
string nome = 2;
double preco = 3;
string categoria = 4;
int32 estoque = 5;
}
message ListaProdutosRequest {
string categoria = 1;
int32 limite = 2;
}
message CriarProdutoRequest {
string nome = 1;
double preco = 2;
string categoria = 3;
int32 estoque = 4;
}
message AtualizarEstoqueRequest {
string id = 1;
int32 quantidade = 2;
}
message EstoqueResponse {
string id = 1;
int32 estoque_anterior = 2;
int32 estoque_atual = 3;
}
// Serviço
service ProdutoService {
// Unário: busca um produto por ID
rpc BuscarProduto(ProdutoRequest) returns (ProdutoResponse);
// Unário: cria um novo produto
rpc CriarProduto(CriarProdutoRequest) returns (ProdutoResponse);
// Server streaming: lista produtos por categoria
rpc ListarProdutos(ListaProdutosRequest) returns (stream ProdutoResponse);
// Client streaming: atualiza estoque em lote
rpc AtualizarEstoqueLote(stream AtualizarEstoqueRequest) returns (EstoqueResponse);
}
Ao compilar com ./gradlew generateProto, o plugin gera automaticamente classes Kotlin com suporte a coroutines — métodos suspend para chamadas unárias e Flow para streaming.
Implementando o servidor
Com o código gerado, implemente o serviço estendendo a classe base:
// src/main/kotlin/com/exemplo/ProdutoServiceImpl.kt
package com.exemplo
import com.exemplo.grpc.*
import io.grpc.Status
import io.grpc.StatusException
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class ProdutoServiceImpl : ProdutoServiceGrpcKt.ProdutoServiceCoroutineImplBase() {
// Simulando um banco de dados em memória
private val produtos = mutableMapOf(
"1" to ProdutoData("1", "Teclado Mecânico", 349.90, "Periféricos", 50),
"2" to ProdutoData("2", "Monitor 4K 27\"", 2199.00, "Monitores", 15),
"3" to ProdutoData("3", "Mouse Gamer", 189.90, "Periféricos", 80),
"4" to ProdutoData("4", "Webcam Full HD", 299.90, "Periféricos", 30),
"5" to ProdutoData("5", "Monitor Ultrawide", 3499.00, "Monitores", 8),
)
// RPC unário: busca produto por ID
override suspend fun buscarProduto(request: ProdutoRequest): ProdutoResponse {
val produto = produtos[request.id]
?: throw StatusException(
Status.NOT_FOUND.withDescription("Produto ${request.id} não encontrado")
)
return produto.toResponse()
}
// RPC unário: cria novo produto
override suspend fun criarProduto(request: CriarProdutoRequest): ProdutoResponse {
val id = (produtos.size + 1).toString()
val novo = ProdutoData(
id = id,
nome = request.nome,
preco = request.preco,
categoria = request.categoria,
estoque = request.estoque
)
produtos[id] = novo
return novo.toResponse()
}
// Server streaming: envia produtos um a um
override fun listarProdutos(request: ListaProdutosRequest): Flow<ProdutoResponse> = flow {
val filtrados = produtos.values
.filter { it.categoria.equals(request.categoria, ignoreCase = true) }
.take(if (request.limite > 0) request.limite else Int.MAX_VALUE)
for (produto in filtrados) {
emit(produto.toResponse())
delay(100) // Simula latência de processamento
}
}
// Client streaming: recebe atualizações em lote
override suspend fun atualizarEstoqueLote(
requests: Flow<AtualizarEstoqueRequest>
): EstoqueResponse {
var totalAtualizado = 0
var ultimoId = ""
requests.collect { request ->
val produto = produtos[request.id]
if (produto != null) {
val estoqueAnterior = produto.estoque
produto.estoque += request.quantidade
totalAtualizado++
ultimoId = request.id
}
}
return estoqueResponse {
id = "lote"
estoqueAnterior = totalAtualizado
estoqueAtual = totalAtualizado
}
}
}
// Data class interna
data class ProdutoData(
val id: String,
val nome: String,
val preco: Double,
val categoria: String,
var estoque: Int
) {
fun toResponse(): ProdutoResponse = produtoResponse {
id = this@ProdutoData.id
nome = this@ProdutoData.nome
preco = this@ProdutoData.preco
categoria = this@ProdutoData.categoria
estoque = this@ProdutoData.estoque
}
}
Repare como o método listarProdutos retorna um Flow<ProdutoResponse> — é a integração nativa do gRPC-Kotlin com Kotlin Flow. O servidor emite respostas uma a uma, e o cliente as recebe de forma reativa.
Iniciando o servidor
// src/main/kotlin/com/exemplo/Main.kt
package com.exemplo
import io.grpc.ServerBuilder
fun main() {
val porta = 50051
val server = ServerBuilder
.forPort(porta)
.addService(ProdutoServiceImpl())
.build()
server.start()
println("Servidor gRPC rodando na porta $porta")
// Graceful shutdown
Runtime.getRuntime().addShutdownHook(Thread {
println("Desligando servidor gRPC...")
server.shutdown()
})
server.awaitTermination()
}
Implementando o cliente
O cliente também usa coroutines nativamente:
// src/main/kotlin/com/exemplo/ProdutoClient.kt
package com.exemplo
import com.exemplo.grpc.*
import io.grpc.ManagedChannelBuilder
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val channel = ManagedChannelBuilder
.forAddress("localhost", 50051)
.usePlaintext()
.build()
val stub = ProdutoServiceGrpcKt.ProdutoServiceCoroutineStub(channel)
// 1. Chamada unária: buscar produto
println("=== Buscar Produto ===")
val produto = stub.buscarProduto(produtoRequest { id = "1" })
println("Encontrado: ${produto.nome} — R$ ${produto.preco}")
// 2. Chamada unária: criar produto
println("\n=== Criar Produto ===")
val novo = stub.criarProduto(criarProdutoRequest {
nome = "SSD NVMe 1TB"
preco = 499.90
categoria = "Armazenamento"
estoque = 25
})
println("Criado: ${novo.nome} (ID: ${novo.id})")
// 3. Server streaming: listar produtos
println("\n=== Listar Periféricos ===")
stub.listarProdutos(listaProdutosRequest {
categoria = "Periféricos"
limite = 10
}).collect { p ->
println(" → ${p.nome}: R$ ${p.preco} (${p.estoque} em estoque)")
}
// 4. Client streaming: atualizar estoque em lote
println("\n=== Atualizar Estoque em Lote ===")
val atualizacoes = flow {
emit(atualizarEstoqueRequest { id = "1"; quantidade = 10 })
emit(atualizarEstoqueRequest { id = "3"; quantidade = -5 })
emit(atualizarEstoqueRequest { id = "4"; quantidade = 20 })
}
val resultado = stub.atualizarEstoqueLote(atualizacoes)
println("Itens atualizados: ${resultado.estoqueAtual}")
channel.shutdown()
}
Perceba que o cliente collect o streaming do servidor da mesma forma que faria com qualquer Flow no Kotlin — sem callbacks, sem listeners, sem boilerplate.
gRPC vs REST: quando usar cada um
A pergunta não é qual é “melhor”, mas qual se encaixa no cenário:
| Critério | REST | gRPC |
|---|---|---|
| Formato | JSON (texto) | Protobuf (binário) |
| Protocolo | HTTP/1.1 ou 2 | HTTP/2 obrigatório |
| Streaming | SSE, WebSocket | Nativo (4 tipos) |
| Geração de código | Opcional (OpenAPI) | Obrigatória (protoc) |
| Browser | Nativo | Precisa de gRPC-Web |
| Performance | Boa | Excelente |
| Ferramentas | Postman, curl | grpcurl, BloomRPC |
Use gRPC quando:
- Comunicação entre microsserviços internos
- Alta throughput e baixa latência são críticos
- Streaming bidirecional é necessário
- Contratos fortes entre serviços são prioridade
Use REST quando:
- API pública consumida por browsers
- Integração com serviços de terceiros
- Time mais familiarizado com REST
Se seu cenário é microsserviços, veja também nosso guia de microsserviços com Kotlin e o artigo sobre Kotlin com Kubernetes.
Tratamento de erros no gRPC
O gRPC tem um sistema de status codes próprio. Veja como usar em Kotlin:
import io.grpc.Status
import io.grpc.StatusException
override suspend fun buscarProduto(request: ProdutoRequest): ProdutoResponse {
if (request.id.isBlank()) {
throw StatusException(
Status.INVALID_ARGUMENT.withDescription("ID não pode ser vazio")
)
}
val produto = produtos[request.id]
?: throw StatusException(
Status.NOT_FOUND.withDescription("Produto não encontrado: ${request.id}")
)
return produto.toResponse()
}
Os códigos mais usados:
| Status | Código | Uso |
|---|---|---|
OK | 0 | Sucesso |
INVALID_ARGUMENT | 3 | Parâmetros inválidos |
NOT_FOUND | 5 | Recurso inexistente |
ALREADY_EXISTS | 6 | Duplicata |
PERMISSION_DENIED | 7 | Sem permissão |
INTERNAL | 13 | Erro interno |
UNAVAILABLE | 14 | Serviço temporariamente fora |
Para quem vem do mundo REST, é similar aos HTTP status codes, mas com semântica mais precisa. Combinado com observabilidade, você monitora cada RPC com métricas detalhadas.
Interceptors: middleware para gRPC
Assim como middleware em REST, interceptors permitem lógica transversal:
import io.grpc.*
class LoggingInterceptor : ServerInterceptor {
override fun <ReqT, RespT> interceptCall(
call: ServerCall<ReqT, RespT>,
headers: Metadata,
next: ServerCallHandler<ReqT, RespT>
): ServerCall.Listener<ReqT> {
val metodo = call.methodDescriptor.fullMethodName
val inicio = System.currentTimeMillis()
println("→ gRPC chamada: $metodo")
return object : ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(
next.startCall(call, headers)
) {
override fun onComplete() {
val duracao = System.currentTimeMillis() - inicio
println("← gRPC resposta: $metodo (${duracao}ms)")
super.onComplete()
}
}
}
}
// Registrando no servidor
val server = ServerBuilder
.forPort(50051)
.addService(ServerInterceptors.intercept(ProdutoServiceImpl(), LoggingInterceptor()))
.build()
Para monitoramento em produção, combine interceptors com OpenTelemetry e Tracy para tracing distribuído completo.
Testando serviços gRPC
Teste seus serviços gRPC com o grpc-testing:
import io.grpc.inprocess.InProcessChannelBuilder
import io.grpc.inprocess.InProcessServerBuilder
import io.grpc.testing.GrpcCleanupRule
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class ProdutoServiceTest {
@get:Rule
val grpcCleanup = GrpcCleanupRule()
@Test
fun `deve buscar produto existente`() = runTest {
val serverName = InProcessServerBuilder.generateName()
grpcCleanup.register(
InProcessServerBuilder.forName(serverName)
.directExecutor()
.addService(ProdutoServiceImpl())
.build()
.start()
)
val channel = grpcCleanup.register(
InProcessChannelBuilder.forName(serverName)
.directExecutor()
.build()
)
val stub = ProdutoServiceGrpcKt.ProdutoServiceCoroutineStub(channel)
val response = stub.buscarProduto(produtoRequest { id = "1" })
assertEquals("Teclado Mecânico", response.nome)
assertEquals(349.90, response.preco)
}
}
Usando InProcessServer, os testes rodam sem abrir porta de rede — rápidos e isolados. Se quiser aprofundar em testes, confira nosso guia de testes com JUnit5 e MockK.
Perguntas Frequentes
gRPC funciona com Kotlin Multiplatform?
Ainda não de forma oficial. O gRPC-Kotlin roda na JVM (servidor e Android). Para KMP com iOS e Web, alternativas como Ktor Client com serialização protobuf são mais viáveis atualmente.
Posso usar gRPC com Spring Boot?
Sim. O Spring Boot tem suporte via grpc-spring-boot-starter. Você combina a injeção de dependência do Spring com os stubs gRPC gerados.
gRPC substitui REST completamente?
Não. Para APIs públicas consumidas por browsers, REST ainda é o padrão. gRPC brilha na comunicação service-to-service em arquiteturas de microsserviços.
Qual a diferença entre gRPC e Ktor RPC?
O Ktor agora oferece o Kotlin RPC — uma alternativa nativa em Kotlin puro, sem protobuf. É mais simples para projetos que já usam Ktor, mas o gRPC tem ecossistema mais maduro e interop com Go, Java, Python, C++.
Como monitorar serviços gRPC em produção?
Use interceptors com OpenTelemetry para tracing distribuído. Combine com observabilidade Kotlin e dashboards como Grafana para métricas de latência, throughput e taxa de erros.
Conclusão
O gRPC com Kotlin e coroutines é uma combinação poderosa para serviços backend de alta performance. A geração de código garante contratos fortes, o streaming nativo via Flow simplifica cenários reativos, e a performance do protobuf faz diferença real em produção.
Se você está começando no backend Kotlin, recomendo primeiro dominar coroutines e Flow antes de mergulhar no gRPC. E para quem já está construindo microsserviços, explore como combinar gRPC com Docker e Kubernetes para uma stack de produção completa.
Vale saber que o gRPC é amplamente usado em Go e Python — se seu time é poliglota, o gRPC permite interoperabilidade nativa entre serviços Kotlin e essas linguagens via protobuf.
Quer explorar mais sobre o ecossistema? Veja as tendências do Kotlin para 2026 e o roadmap para dev backend.