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érioRESTgRPC
FormatoJSON (texto)Protobuf (binário)
ProtocoloHTTP/1.1 ou 2HTTP/2 obrigatório
StreamingSSE, WebSocketNativo (4 tipos)
Geração de códigoOpcional (OpenAPI)Obrigatória (protoc)
BrowserNativoPrecisa de gRPC-Web
PerformanceBoaExcelente
FerramentasPostman, curlgrpcurl, 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:

StatusCódigoUso
OK0Sucesso
INVALID_ARGUMENT3Parâmetros inválidos
NOT_FOUND5Recurso inexistente
ALREADY_EXISTS6Duplicata
PERMISSION_DENIED7Sem permissão
INTERNAL13Erro interno
UNAVAILABLE14Serviç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.