gRPC
High-performance RPC over HTTP/2 with strongly typed Protobuf contracts.
Definition
gRPC is a Remote Procedure Call framework using HTTP/2 transport and Protocol Buffers (protobuf) for interface definition and payload serialization. Clients and servers are generated from .proto contracts.
Why It Matters
- Compact binary payloads reduce network overhead vs JSON in many workloads.
- IDL-first design enforces type-safe API contracts across teams/languages.
- Built-in streaming modes support real-time bidirectional communication.
- HTTP/2 multiplexing improves connection utilization for high-QPS systems.
When to Use
- Internal microservices with strict latency and type-safety requirements.
- Multi-language backend platforms needing shared IDL contracts.
- Workloads requiring streaming communication patterns.
When NOT to Use
- Public browser-heavy APIs without gateway/transcoding plan.
- Teams without protobuf/versioning discipline or observability maturity.
Code Variations
syntax = "proto3";
package users.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc StreamUsers(StreamUsersRequest) returns (stream UserEvent);
}
message GetUserRequest { string user_id = 1; }
message GetUserResponse { string user_id = 1; string name = 2; string email = 3; }
message StreamUsersRequest { string tenant_id = 1; }
message UserEvent { string user_id = 1; string action = 2; int64 ts_epoch_ms = 3; }@GrpcService
public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(GetUserRequest req, StreamObserver<GetUserResponse> obs) {
try {
User u = userRepo.load(req.getUserId());
obs.onNext(GetUserResponse.newBuilder()
.setUserId(u.id()).setName(u.name()).setEmail(u.email()).build());
obs.onCompleted();
} catch (NotFoundException e) {
obs.onError(Status.NOT_FOUND
.withDescription("user not found")
.asRuntimeException());
}
}
}Metadata md = new Metadata();
md.put(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER),
"Bearer " + token);
UserServiceGrpc.UserServiceBlockingStub stub =
MetadataUtils.attachHeaders(baseStub, md)
.withDeadlineAfter(300, TimeUnit.MILLISECONDS);
GetUserResponse res = stub.getUser(
GetUserRequest.newBuilder().setUserId("u-123").build()
);NOT_FOUND -> entity missing
INVALID_ARGUMENT -> validation failed
UNAUTHENTICATED -> auth missing/invalid
PERMISSION_DENIED-> auth valid but forbidden
UNAVAILABLE -> transient infra/network issue
DEADLINE_EXCEEDED-> timeoutgRPC capabilities and design decisions
| Aspect | What gRPC gives | What you must design carefully |
|---|---|---|
| Contracts | Strong .proto schema + code generation | Backward-compatible schema evolution rules |
| Performance | Protobuf + HTTP/2 efficiency | Tune deadlines, connection pools, message sizes |
| Streaming | Unary + server/client/bidi streaming | Flow control and backpressure handling |
| Reliability | Rich status model + retry support | Idempotency + retry policy boundaries |
| Security | TLS/mTLS + metadata auth headers | Authn/z propagation and secret rotation |
Common Confusions Cleared Up
gRPC often excels inside trusted service mesh. REST still dominates external/public APIs due to browser/tooling interoperability. Many systems run hybrid: REST at edge, gRPC internally.
Only retry idempotent operations automatically. Non-idempotent writes need idempotency keys/transaction semantics before enabling aggressive retry policies.
Backward compatibility requires discipline: do not change field types casually, do not reuse tag numbers, and phase-out fields with reserve/deprecation strategy.
Variations / Implementations
Unary RPC
Single request -> single response. Most common pattern for internal synchronous calls.
Server streaming
One request -> many responses. Useful for large result sets, live feeds, incremental updates.
Client streaming
Many requests -> one response. Useful for batch ingestion/aggregation.
Bidirectional streaming
Both sides stream concurrently. Useful for chat, collaborative sessions, telemetry pipelines.
Common Mistakes / Anti-Patterns
- No deadlines on client calls.
- Mapping all errors to
INTERNAL(poor debuggability). - Breaking protobuf compatibility during schema changes.
- Retrying non-idempotent writes blindly.
- Skipping mTLS/auth metadata in internal zero-trust environments.