Thursday, July 3, 2025

📡 Mastering Advanced gRPC Communication Patterns

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

📐 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; }
    ✅ Real-world Example Code Repository 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

    📐 Diagram

            

    📜 Protobuf Definition
    • rpc StreamStockPrices(StockRequest) returns (stream StockPrice);
    🧩 Real-world Example
    • message StockRequest { string symbol = 1; } message StockPrice { string symbol = 1; double price = 2; string timestamp = 3; }
    🚀 Java Server Snippetl
    • @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

      📐 Diagram

              

      📜 Protobuf Definition:
      • rpc UploadFile(stream FileChunk) returns (UploadStatus);
      🧩 Real-world Example:
      • message FileChunk { bytes data = 1; } message UploadStatus { string status = 1; int64 total_bytes = 2; }
      🚀 Java Server Snippet:
      • @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

        📐 Diagram

                

        📜 Protobuf Definition:
        • rpc Chat(stream ChatMessage) returns (stream ChatMessage);
        🧩 Real-world Example:
        • message ChatMessage { string sender = 1; string message = 2; string timestamp = 3; }
        🚀 Java Server Snippet:
        • @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 


          You may also like

          Kubernetes Microservices
          Python AI/ML
          Spring Framework Spring Boot
          Core Java Java Coding Question
          Maven AWS