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.
