gRPC Four Communication Types

gRPC supports four distinct ways for a client and server to exchange messages. Each pattern suits a different real-world scenario. Choosing the right pattern dramatically affects how clean and efficient your code is.

Overview of the Four Patterns

┌──────────────────────────┬───────────────┬───────────────────────────┐
│ Pattern                  │ Client Sends  │ Server Sends              │
├──────────────────────────┼───────────────┼───────────────────────────┤
│ Unary                    │ 1 message     │ 1 message                 │
│ Server Streaming         │ 1 message     │ many messages             │
│ Client Streaming         │ many messages │ 1 message                 │
│ Bidirectional Streaming  │ many messages │ many messages             │
└──────────────────────────┴───────────────┴───────────────────────────┘

Pattern 1: Unary RPC

Unary is the simplest pattern. The client sends one request and waits for one response. This is the gRPC equivalent of a traditional REST API call.

Client                          Server
  │                                │
  │──── GetUser(userId=42) ───────►│
  │                                │  (looks up user 42)
  │◄─── User{id:42, name:"Alice"} ─│
  │                                │

Proto definition:
  rpc GetUser (GetUserRequest) returns (User);
  // No "stream" keyword = unary

Real-World Uses for Unary

• Fetching a user profile
• Authenticating a login token
• Retrieving a single order
• Looking up a product price
• Processing a one-time payment

Unary Proto Example

service AccountService {
  rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse);
  rpc Transfer   (TransferRequest)   returns (TransferResponse);
}

message GetBalanceRequest  { int32 account_id = 1; }
message GetBalanceResponse { double balance    = 1; }

Pattern 2: Server Streaming RPC

The client sends one request and the server sends back many messages, one at a time, until the stream closes. The client reads each message as it arrives — no waiting for all data to accumulate.

Client                          Server
  │                                │
  │──── WatchStockPrice(AAPL) ────►│
  │                                │  (starts sending prices)
  │◄─── Price{value: 182.00} ──────│
  │◄─── Price{value: 182.45} ──────│
  │◄─── Price{value: 181.90} ──────│
  │◄─── Price{value: 183.10} ──────│
  │          ... (continues) ...   │
  │◄─── [stream closes] ───────────│

Proto definition:
  rpc WatchStockPrice (StockRequest) returns (stream PriceUpdate);
  //                                          ▲
  //                             "stream" keyword makes it streaming

Real-World Uses for Server Streaming

• Live stock or cryptocurrency price feeds
• Downloading a large dataset row by row
• Log streaming (server pushes new log lines)
• Monitoring dashboards (CPU, memory metrics)
• Sending a large file in chunks

Server Streaming Proto Example

service NotificationService {
  rpc StreamNotifications (StreamRequest) returns (stream Notification);
}

message StreamRequest {
  int32 user_id   = 1;
  int32 max_count = 2;   // 0 means unlimited
}

message Notification {
  string title   = 1;
  string body    = 2;
  int64  sent_at = 3;
}

Pattern 3: Client Streaming RPC

The client sends many messages one after another, then closes the stream. The server reads all of them and sends back one final response. This pattern is ideal for sending large data in chunks or batches.

Client                          Server
  │                                │
  │──── UploadChunk (bytes 0-4K) ─►│
  │──── UploadChunk (bytes 4-8K) ─►│
  │──── UploadChunk (bytes 8-12K)─►│
  │──── [closes stream] ───────────│
  │                                │  (assembles file, saves it)
  │◄─── UploadResult{url: "..."} ──│

Proto definition:
  rpc UploadFile (stream FileChunk) returns (UploadResult);
  //              ▲
  //  "stream" before the request type = client streaming

Real-World Uses for Client Streaming

• Uploading large files in chunks
• Sending telemetry data from IoT sensors in batches
• Bulk inserting records into a database
• Streaming audio/video frames from a device to a server
• Aggregating log events from a client

Client Streaming Proto Example

service DataIngestionService {
  rpc IngestEvents (stream SensorEvent) returns (IngestSummary);
}

message SensorEvent {
  string device_id  = 1;
  double temperature = 2;
  double humidity    = 3;
  int64  timestamp   = 4;
}

message IngestSummary {
  int32 events_received = 1;
  int32 events_failed   = 2;
}

Pattern 4: Bidirectional Streaming RPC

Both the client and the server send streams of messages at the same time, independently. Neither side has to wait for the other. This is the most powerful and complex pattern.

Client                          Server
  │                                │
  │──── Message("Hello") ─────────►│
  │◄─── Message("Hi there") ───────│
  │──── Message("How are you?") ──►│
  │◄─── Message("All good!") ──────│
  │──── Message("Bye") ───────────►│
  │◄─── Message("Goodbye!") ───────│
  │──── [client closes stream] ────│
  │◄─── [server closes stream] ────│

Both sides read and write at the same time on the same HTTP/2 stream.

Proto definition:
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
  //        ▲                            ▲
  //  both request AND response are streams

Real-World Uses for Bidirectional Streaming

• Real-time chat applications
• Multiplayer game state synchronisation
• Collaborative document editing
• Trading systems (send orders, receive fills simultaneously)
• Voice and video calls (audio frames flow in both directions)

Bidirectional Streaming Proto Example

service TradingService {
  rpc TradeStream (stream TradeOrder) returns (stream TradeConfirmation);
}

message TradeOrder {
  string symbol   = 1;
  string side     = 2;   // "BUY" or "SELL"
  int32  quantity = 3;
  double price    = 4;
}

message TradeConfirmation {
  string order_id    = 1;
  string status      = 2;
  double filled_price = 3;
}

All Four Patterns Together

service AnalyticsService {
  // 1. Unary — fetch a single report
  rpc GetReport (GetReportRequest) returns (Report);

  // 2. Server streaming — stream report rows as they are generated
  rpc StreamReport (GetReportRequest) returns (stream ReportRow);

  // 3. Client streaming — upload raw events, get a summary
  rpc UploadEvents (stream RawEvent) returns (UploadSummary);

  // 4. Bidirectional — interactive query session
  rpc QuerySession (stream Query) returns (stream QueryResult);
}

Choosing the Right Pattern

Ask these questions:
  Does the client send exactly one thing?
    Yes → client side is NOT streaming
    No  → client side IS streaming

  Does the server send exactly one thing?
    Yes → server side is NOT streaming
    No  → server side IS streaming

Matrix:
  Client: 1,  Server: 1  →  Unary
  Client: 1,  Server: N  →  Server Streaming
  Client: N,  Server: 1  →  Client Streaming
  Client: N,  Server: N  →  Bidirectional Streaming

Summary

Unary handles simple request-response cycles. Server streaming pushes data to the client over time. Client streaming lets the client send bulk data before getting a final answer. Bidirectional streaming enables real-time two-way communication. The stream keyword in the proto file controls which pattern a method uses.

Leave a Comment

Your email address will not be published. Required fields are marked *