🚄 gRPC Communication
High-performance, strongly-typed inter-service communication using Protocol Buffers — faster than REST, with streaming support and code generation.
Real-World Analogy
REST is like sending letters — human-readable, flexible format, but slow to write and parse. gRPC is like a direct phone line — pre-agreed protocol, extremely fast, supports two-way conversations (streaming). You sacrifice human readability for raw performance.
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
subgraph REST["REST/JSON"]
R1["Client"] -->|"HTTP/1.1<br/>JSON text<br/>~100ms"| R2["Server"]
end
subgraph GRPC["gRPC"]
G1["Client"] -->|"HTTP/2<br/>Protobuf binary<br/>~10ms"| G2["Server"]
end
style REST fill:#FFCDD2,stroke:#C62828,color:#000
style GRPC fill:#E8F5E9,stroke:#2E7D32,color:#000 🏗️ Architecture
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
subgraph Define["1️⃣ Define Contract"]
Proto[/"📜 .proto file<br/>(service + messages)"/]
end
subgraph Generate["2️⃣ Code Generation"]
Proto --> GS[["Generated Server Stub<br/>(Java)"]]
Proto --> GC[["Generated Client Stub<br/>(Java/Go/Python)"]]
end
subgraph Runtime["3️⃣ Runtime"]
GC --> |"HTTP/2 + Protobuf"| GS
end
style Proto fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#000 📜 Protocol Buffer Definition
syntax = "proto3";
package com.example.order;
option java_multiple_files = true;
option java_package = "com.example.order.grpc";
// Service definition
service OrderService {
// Unary RPC
rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
rpc GetOrder (GetOrderRequest) returns (OrderResponse);
// Server streaming
rpc GetOrderUpdates (GetOrderRequest) returns (stream OrderStatusUpdate);
// Client streaming
rpc BatchCreateOrders (stream CreateOrderRequest) returns (BatchOrderResponse);
// Bidirectional streaming
rpc OrderChat (stream OrderMessage) returns (stream OrderMessage);
}
// Messages
message CreateOrderRequest {
string user_id = 1;
repeated OrderItem items = 2;
string shipping_address = 3;
PaymentMethod payment_method = 4;
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
double unit_price = 3;
}
message OrderResponse {
string order_id = 1;
string status = 2;
double total_amount = 3;
google.protobuf.Timestamp created_at = 4;
}
enum PaymentMethod {
CREDIT_CARD = 0;
DEBIT_CARD = 1;
WALLET = 2;
}
message OrderStatusUpdate {
string order_id = 1;
string status = 2;
string message = 3;
google.protobuf.Timestamp timestamp = 4;
}
🖥️ Server Implementation (Spring Boot + grpc-spring-boot-starter)
Dependencies
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
Service Implementation
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
private final OrderService orderService;
@Override
public void createOrder(CreateOrderRequest request, StreamObserver<OrderResponse> responseObserver) {
try {
Order order = orderService.create(toOrderCommand(request));
OrderResponse response = OrderResponse.newBuilder()
.setOrderId(order.getId())
.setStatus(order.getStatus().name())
.setTotalAmount(order.getTotal().doubleValue())
.setCreatedAt(toTimestamp(order.getCreatedAt()))
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
} catch (Exception e) {
responseObserver.onError(Status.INTERNAL
.withDescription(e.getMessage())
.asRuntimeException());
}
}
// Server streaming — push real-time order updates
@Override
public void getOrderUpdates(GetOrderRequest request,
StreamObserver<OrderStatusUpdate> responseObserver) {
String orderId = request.getOrderId();
orderService.subscribeToUpdates(orderId, update -> {
responseObserver.onNext(OrderStatusUpdate.newBuilder()
.setOrderId(orderId)
.setStatus(update.status())
.setMessage(update.message())
.setTimestamp(toTimestamp(update.timestamp()))
.build());
});
// Complete when order reaches terminal state
}
}
📡 Client Implementation
@Service
public class OrderGrpcClient {
private final OrderServiceGrpc.OrderServiceBlockingStub blockingStub;
private final OrderServiceGrpc.OrderServiceStub asyncStub;
public OrderGrpcClient(@GrpcClient("order-service") Channel channel) {
this.blockingStub = OrderServiceGrpc.newBlockingStub(channel);
this.asyncStub = OrderServiceGrpc.newStub(channel);
}
// Unary call (blocking)
public OrderResponse createOrder(String userId, List<OrderItem> items) {
CreateOrderRequest request = CreateOrderRequest.newBuilder()
.setUserId(userId)
.addAllItems(items)
.build();
return blockingStub
.withDeadlineAfter(5, TimeUnit.SECONDS) // Timeout
.createOrder(request);
}
// Server streaming (async)
public void subscribeToOrderUpdates(String orderId, Consumer<OrderStatusUpdate> callback) {
GetOrderRequest request = GetOrderRequest.newBuilder()
.setOrderId(orderId)
.build();
asyncStub.getOrderUpdates(request, new StreamObserver<>() {
@Override
public void onNext(OrderStatusUpdate update) {
callback.accept(update);
}
@Override
public void onError(Throwable t) {
log.error("Stream error for order: {}", orderId, t);
}
@Override
public void onCompleted() {
log.info("Order updates stream completed: {}", orderId);
}
});
}
}
Client Configuration
grpc:
client:
order-service:
address: dns:///order-service:9090
negotiation-type: TLS
enable-keep-alive: true
keep-alive-time: 30s
📊 Communication Patterns
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
subgraph Patterns["gRPC Communication Patterns"]
U(["1️⃣ Unary<br/>Request → Response"])
SS(["2️⃣ Server Streaming<br/>Request → Stream of Responses"])
CS(["3️⃣ Client Streaming<br/>Stream of Requests → Response"])
BD(["4️⃣ Bidirectional<br/>Stream ↔ Stream"])
end
style U fill:#E8F5E9,stroke:#2E7D32,color:#000
style SS fill:#E3F2FD,stroke:#1565C0,color:#000
style CS fill:#FEF3C7,stroke:#D97706,color:#000
style BD fill:#F3E5F5,stroke:#6A1B9A,color:#000 | Pattern | Use Case | Example |
|---|---|---|
| Unary | Simple request-response | Get order, create user |
| Server Streaming | Real-time updates from server | Price feeds, order status |
| Client Streaming | Batch uploads from client | Bulk data import, log collection |
| Bidirectional | Real-time two-way communication | Chat, collaborative editing |
⚠️ Error Handling
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
@Override
public void getOrder(GetOrderRequest request, StreamObserver<OrderResponse> observer) {
try {
Order order = orderService.findById(request.getOrderId())
.orElseThrow(() -> Status.NOT_FOUND
.withDescription("Order not found: " + request.getOrderId())
.asRuntimeException());
observer.onNext(toResponse(order));
observer.onCompleted();
} catch (StatusRuntimeException e) {
observer.onError(e); // gRPC status errors pass through
} catch (IllegalArgumentException e) {
observer.onError(Status.INVALID_ARGUMENT
.withDescription(e.getMessage())
.asRuntimeException());
} catch (Exception e) {
observer.onError(Status.INTERNAL
.withDescription("Unexpected error")
.asRuntimeException());
}
}
}
gRPC Status Codes
| Code | HTTP Equivalent | Use When |
|---|---|---|
OK | 200 | Success |
INVALID_ARGUMENT | 400 | Bad request |
NOT_FOUND | 404 | Resource doesn't exist |
ALREADY_EXISTS | 409 | Duplicate |
PERMISSION_DENIED | 403 | Not authorized |
UNAUTHENTICATED | 401 | Not authenticated |
DEADLINE_EXCEEDED | 408 | Timeout |
UNAVAILABLE | 503 | Service down |
INTERNAL | 500 | Server error |
🔧 Interceptors (Middleware)
// Server interceptor for logging and metrics
@GrpcGlobalServerInterceptor
public class LoggingInterceptor implements ServerInterceptor {
@Override
public <Req, Resp> ServerCall.Listener<Req> interceptCall(
ServerCall<Req, Resp> call,
Metadata headers,
ServerCallHandler<Req, Resp> next) {
String method = call.getMethodDescriptor().getFullMethodName();
Instant start = Instant.now();
log.info("gRPC call started: {}", method);
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<>(
next.startCall(call, headers)) {
@Override
public void onComplete() {
long duration = Duration.between(start, Instant.now()).toMillis();
log.info("gRPC call completed: {} ({}ms)", method, duration);
super.onComplete();
}
};
}
}
📊 gRPC vs REST Comparison
| Feature | gRPC | REST |
|---|---|---|
| Protocol | HTTP/2 | HTTP/1.1 (usually) |
| Payload | Protocol Buffers (binary) | JSON (text) |
| Speed | ~10x faster serialization | Human-readable |
| Contract | Strict (.proto) | Flexible (OpenAPI optional) |
| Streaming | Native (4 patterns) | Workarounds (SSE, WebSocket) |
| Code Generation | Built-in (all languages) | Optional (codegen tools) |
| Browser Support | Limited (needs grpc-web) | Universal |
| Tooling | grpcurl, Postman (newer) | curl, Postman, browsers |
| Best For | Internal microservice calls | Public APIs, web clients |
🎯 Interview Questions
1. When would you choose gRPC over REST?
Choose gRPC for: internal service-to-service communication where performance matters, polyglot environments (auto-generated clients), streaming use cases (real-time updates), strict contracts (API evolution safety). Keep REST for: public APIs, browser clients, simple CRUD, when human readability matters.
2. What are the four gRPC communication patterns?
Unary (request → response), Server Streaming (request → stream of responses), Client Streaming (stream of requests → response), Bidirectional Streaming (stream ↔ stream). Each serves different use cases from simple RPC to real-time bidirectional communication.
3. How does gRPC achieve better performance than REST?
Three reasons: HTTP/2 (multiplexing, header compression, single connection), Protocol Buffers (binary serialization, 3-10x smaller and faster than JSON), Connection reuse (persistent connections, no handshake per request). Combined, gRPC is typically 7-10x faster than REST for serialization/deserialization.
4. How do you handle errors in gRPC?
Use Status codes (similar to HTTP status codes but richer). Return StatusRuntimeException with appropriate code (NOT_FOUND, INVALID_ARGUMENT, INTERNAL). Add descriptive messages. For rich error details, use com.google.rpc.Status with attached Any messages containing error-specific metadata.
5. How do you handle API evolution in gRPC?
Protocol Buffers have built-in backward compatibility rules: never reuse field numbers, add new fields (old clients ignore them), deprecate fields (don't remove). Use reserved to prevent reuse of removed field numbers. Additive changes are always safe.
6. How do you secure gRPC communication?
TLS for transport encryption (mandatory in production). Interceptors for authentication (extract JWT from metadata headers). mTLS for mutual authentication between services. Deadline propagation prevents cascading timeouts. Use CallCredentials for per-call auth tokens.