Overview
gRPC is contract-first binary RPC: services and messages are declared in protobuf .proto
files, code is generated for every language, and calls run over HTTP/2 with multiplexing
and native streaming. On the JVM the canonical runtime is grpc-java; the frameworks
differ mainly in how they host it — Spring through a starter, Quarkus through a deeply
integrated extension with reactive stubs, Micronaut through compile-time wiring. It shines
for internal service-to-service traffic where latency, streaming and cross-language
contracts matter; it is a poor fit for browsers, which is why gateways exist.
Key points
- The contract is the
.protofile:messageandservicedefinitions, evolved by field-number discipline — never reuse or renumber a field; add optional fields instead. - Codegen:
protocplus the grpc-java plugin, driven from the build — the protobuf Gradle plugin or the protobuf Maven plugin — producing message classes and stub variants (blocking, future, async). - grpc-java servers run on a Netty transport; an in-process transport makes tests fast and hermetic.
- Four call shapes: unary, server-streaming, client-streaming, bidirectional — all first-class in the IDL, no polling or upgrade tricks.
- Deadlines, not timeouts: the client attaches an absolute deadline that propagates
across hops; servers should check
Contextcancellation. Metadata (headers/trailers) carries auth tokens and correlation ids; errors areStatuscodes + optional details. - Spring: the community starter (grpc-ecosystem's grpc-spring, formerly
grpc-spring-boot-starter) and the newer official Spring gRPC project both wire
@GrpcService-style beans into Boot. - Quarkus gRPC generates Mutiny stubs (
Uni/Multi), supports serving gRPC on the main HTTP port, and registers clients via@GrpcClient. - Micronaut gRPC hosts
BindableServicebeans on its Netty server with compile-time DI. - Browsers can't speak gRPC directly: use gRPC-Web, Envoy's gRPC-JSON transcoding, or the Connect protocol to expose a REST/JSON face.
Details
Build integration
The .proto files live in src/main/proto; the build invokes protoc with the
grpc-java codegen plugin. Gradle uses the protobuf Gradle plugin (com.google.protobuf);
Maven historically the xolstice protobuf-maven-plugin, with maintained forks and
protoc-toolchain alternatives since. Generated sources are build output, not committed.
Keep .proto files in a shared artifact or a schema repo when several services consume
them — the contract's home matters more than the codegen mechanics.
Framework hosting compared
| Concern | Plain grpc-java | Spring (Spring gRPC / grpc-spring) | Quarkus gRPC | Micronaut gRPC |
|---|---|---|---|---|
| Service declaration | extend generated *ImplBase, register on ServerBuilder |
@GrpcService-annotated bean |
@GrpcService bean, Mutiny or *ImplBase |
BindableService bean |
| Stub style | blocking / future / async StreamObserver |
same as grpc-java | Mutiny Uni/Multi |
same as grpc-java |
| Client injection | manual channel + stub | @GrpcClient injection |
@GrpcClient injection |
DI-managed channels |
| Port model | dedicated port | dedicated port | dedicated or unified with HTTP | dedicated port |
| Extras | interceptors | health, reflection, security integration | dev-mode reflection UI, TLS config | service discovery hooks |
Cross-cutting concerns (auth, logging, retries) belong in ServerInterceptor /
ClientInterceptor — the filter model of gRPC — in every stack.
Streaming, deadlines, metadata
Server streaming suits feeds and large result sets; client streaming suits uploads and
aggregation; bidirectional streams are effectively a typed message channel (and compete
with WebSockets inside the data centre). Always set client deadlines — a gRPC call
without a deadline is an unbounded resource hold — and propagate them; grpc-java carries
them in Context. Retries belong only on idempotent methods, configured via service
config, never blanket.
When gRPC over REST
- Internal service mesh: east-west traffic where both ends are yours; meshes and load balancers understand HTTP/2 and gRPC health/metadata natively.
- Streaming: any of the three streaming shapes; REST has no equivalent.
- Polyglot contracts: one
.protogenerates consistent clients for Java, Go, Python… — stronger than sharing an OpenAPI file. - Latency/throughput: binary framing and multiplexing beat JSON parsing at volume.
Prefer REST for public APIs, browser clients, and anywhere human debuggability and ubiquitous tooling outweigh performance; expose gRPC services externally through transcoding rather than forcing gRPC on consumers.
Examples
service OrderService {
rpc GetOrder (GetOrderRequest) returns (Order);
rpc WatchOrders (WatchRequest) returns (stream OrderEvent); // server streaming
}
// Quarkus — Mutiny stub client with a deadline
@GrpcClient("orders")
OrderService client;
Uni<Order> order = client.getOrder(req)
.ifNoItem().after(Duration.ofSeconds(2)).fail();
Related
- Backend protocols in Java — the map — parent overview of the protocol landscape.
- REST & HTTP APIs in Java — the default gRPC is measured against, and the face gateways expose.
- Messaging in Java — Kafka, JMS & AMQP — the asynchronous alternative when the call needn't be a call.
- Microservice architecture — the service-mesh context where gRPC is most at home.