Modern distributed systems demand high-performance, language-agnostic communication. gRPC—Google’s high-speed RPC framework—has become a core enabler of service-to-service communication in microservices architectures. While Unary RPCs serve simple request-response models, gRPC shines in more complex, real-time communication through streaming patterns.

In this post, we deep dive into four powerful gRPC communication patterns:
-
Unary RPC
-
Server Streaming RPC
-
Client Streaming RPC
-
Bidirectional Streaming RPC
1️⃣ Unary RPC – The Standard Request-Response
Unary RPC is the simplest form of gRPC where the client sends one request and gets one response from the server.
📊 Use Case
-
Get user profile by ID
-
Calculate tax for a product
-
Health check API
Get user profile by ID
Calculate tax for a product
Health check API
📐 Diagram
📜 Protobuf Definition
- rpc GetUser(UserRequest) returns (UserResponse);
🧩 Real-world Example
- message UserRequest { string user_id = 1; } message UserResponse { string name = 1; string email = 2; }
✅ Unary RPC blog post Link
2️⃣ Server Streaming RPC – One Request, Many Responses
The client sends a single request to the server and receives a stream of responses.
📊 Use Case
-
Real-time stock updates
-
Event logs streaming
-
Download large files in chunks
Real-time stock updates
Event logs streaming
Download large files in chunks
📐 Diagram
- rpc StreamStockPrices(StockRequest) returns (stream StockPrice);
- message StockRequest { string symbol = 1; } message StockPrice { string symbol = 1; double price = 2; string timestamp = 3; }
- @Override public void streamStockPrices(StockRequest request, StreamObserver<StockPrice> responseObserver) { for (int i = 0; i < 5; i++) { StockPrice price = StockPrice.newBuilder() .setSymbol(request.getSymbol()) .setPrice(Math.random() * 1000) .setTimestamp(Instant.now().toString()) .build(); responseObserver.onNext(price); Thread.sleep(1000); // Simulate delay } responseObserver.onCompleted(); }
✅Server Streaming RPC blog post Link
3️⃣ Client Streaming RPC – Many Requests, One Response
The client sends a stream of requests, and once done, the server processes the input and responds with a single response.
📊 Use Case
-
File upload in chunks
-
Sensor data aggregation
-
Log batch processing
File upload in chunks
Sensor data aggregation
Log batch processing
📐 Diagram
- rpc UploadFile(stream FileChunk) returns (UploadStatus);
- message FileChunk { bytes data = 1; } message UploadStatus { string status = 1; int64 total_bytes = 2; }
- @Override public StreamObserver<FileChunk> uploadFile(StreamObserver<UploadStatus> responseObserver) { return new StreamObserver<FileChunk>() { long totalBytes = 0; @Override public void onNext(FileChunk chunk) { totalBytes += chunk.getData().size(); } @Override public void onCompleted() { UploadStatus status = UploadStatus.newBuilder() .setStatus("SUCCESS") .setTotalBytes(totalBytes) .build(); responseObserver.onNext(status); responseObserver.onCompleted(); } @Override public void onError(Throwable t) { responseObserver.onError(t); } }; }
✅Server Streaming RPC blog post Link
4️⃣ Bidirectional Streaming RPC – Many-to-Many Messaging
The client and server both send streams of messages. Both operate independently, allowing real-time duplex communication.
📊 Use Case
-
Chat applications
-
Multiplayer gaming
-
Real-time trading systems
Chat applications
Multiplayer gaming
Real-time trading systems
📐 Diagram
- rpc Chat(stream ChatMessage) returns (stream ChatMessage);
- message ChatMessage { string sender = 1; string message = 2; string timestamp = 3; }
- @Override public StreamObserver<ChatMessage> chat(StreamObserver<ChatMessage> responseObserver) { return new StreamObserver<ChatMessage>() { @Override public void onNext(ChatMessage msg) { System.out.println("Received: " + msg.getMessage()); ChatMessage reply = ChatMessage.newBuilder() .setSender("Server") .setMessage("Echo: " + msg.getMessage()) .setTimestamp(Instant.now().toString()) .build(); responseObserver.onNext(reply); } @Override public void onCompleted() { responseObserver.onCompleted(); } @Override public void onError(Throwable t) { responseObserver.onError(t); } }; }
✅Server Streaming RPC blog post Link
📌 Summary Comparison
🚫 When Not to Use Streaming RPCs
While powerful, streaming adds complexity. Avoid them when:
-
You don’t need real-time communication.
-
You’re behind intermediaries that don’t support HTTP/2.
-
You require retry logic per message (streaming retries are complex).
-
You need precise observability and tracing per operation.
-
📎 Final Thoughts
Understanding these advanced patterns lets you choose the right tool for the right communication problem. gRPC’s streaming capabilities go far beyond what REST can offer for real-time, high-throughput systems.
If you're building a microservices platform, data pipeline, or real-time analytics dashboard, mastering these patterns will make your communication scalable, resilient, and blazing fast.
📚 Further Reading