When services talk to each other thousands of times per request inside a datacenter, the verbosity of JSON-over-HTTP/1.1 becomes a real tax. gRPC is Google's answer: a high-performance, contract-first RPC framework built on Protocol Buffers (a compact binary format) and HTTP/2. It's the third member of the API trio alongside REST and GraphQL — and where it shines is exactly where they don't: low-latency, high-throughput, polyglot service-to-service communication. It builds directly on the encoding ideas in our DDIA notes on encoding & evolution and complements our GraphQL vs REST comparison.
- Contract-first RPC — you define services and messages in a
.protofile and generate strongly-typed client + server stubs in many languages. - Protocol Buffers — a compact, schema'd binary format; far smaller and faster to parse than JSON, with built-in schema evolution.
- Built on HTTP/2 — multiplexing, binary framing, and header compression; the foundation for streaming.
- Four call types — unary, server-streaming, client-streaming, and bidirectional streaming.
- Best for internal microservices — low latency, high throughput, strict contracts, polyglot teams.
- Weak for public/browser APIs — not human-readable, and browsers need a grpc-web proxy; REST/GraphQL still win at the public edge.
gRPC is a contract-first RPC framework: define your API in a .proto file, generate typed stubs, and call remote methods like local functions. It serializes with Protocol Buffers (compact binary) over HTTP/2, giving low latency, small payloads, and four streaming modes. It's excellent for internal, performance-sensitive, polyglot service-to-service communication, and a poor fit for public APIs or direct browser calls (where REST/GraphQL and human-readable JSON win).
What gRPC Is
gRPC is an RPC (Remote Procedure Call) framework: the goal is to make calling a method on a remote service feel like calling a local function. It's contract-first — you start by writing an interface definition (the .proto) describing the service's methods and message types, then a compiler generates client and server stubs in your languages. The client calls a generated method; the stub handles serialization, the network, and deserialization. (The classic caveat from DDIA still holds: a remote call isn't really like a local one — it can fail or time out — so design for partial failure.)
Protocol Buffers
The payload format is Protocol Buffers (protobuf) — a schema-based binary encoding. You declare typed messages with numbered fields; the encoded bytes carry the field tags, not names, making them compact and fast to parse, with well-defined rules for evolving the schema (add fields with new tags, never reuse a tag).
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListReq) returns (stream User); // server streaming
}
message User {
string id = 1;
string name = 2;
int64 since = 3;
}
From this one file, protoc generates a typed client and server in Go, Java, Python, etc. — the contract is the single source of truth, and both sides are guaranteed to agree on the wire format. Compared to JSON, protobuf is markedly smaller and faster to encode/decode, which is the bulk of gRPC's performance win.
Built on HTTP/2
gRPC runs over HTTP/2, and that choice unlocks most of its capabilities beyond protobuf:
- Multiplexing — many concurrent calls share one TCP connection without head-of-line blocking (unlike HTTP/1.1, which needs a connection per in-flight request).
- Binary framing — the protocol is binary, efficient to parse.
- Header compression (HPACK) — repeated headers cost little.
- Streaming — long-lived bidirectional streams over a single connection, which is what enables gRPC's streaming modes.
The Four Call Types
Because HTTP/2 supports streams in both directions, gRPC offers four method shapes — a real advantage over request/response REST:
unary client → 1 req | 1 resp ← server (normal call)
server-streaming client → 1 req | many resp ← server (feed/results)
client-streaming client → many req | 1 resp ← server (upload/aggregate)
bidirectional client ↔ many | many ↔ server (chat, realtime)
Unary is the everyday request/response. The streaming modes shine for large result sets (server-streaming), uploads/telemetry (client-streaming), and interactive, low-latency exchanges (bidirectional) — use cases that are awkward over plain REST.
Code Generation and Contract-First Development
The workflow flips the usual REST habit of writing endpoints then documenting them. With gRPC the .proto is the API: you design it first, generate stubs for every service, and the compiler enforces that callers and implementers match. The benefits are type safety across language boundaries (a Go service and a Python client share one contract), no hand-written serialization, and the schema doubling as living documentation. The cost is a build step and a shared .proto to version and distribute.
gRPC vs REST vs GraphQL
| Aspect | gRPC | REST | GraphQL |
|---|---|---|---|
| Payload | Protobuf (binary) | JSON (text) | JSON (text) |
| Transport | HTTP/2 | HTTP/1.1 or 2 | HTTP |
| Contract | Strict (.proto) | Loose (OpenAPI optional) | Strict (schema) |
| Streaming | First-class (4 modes) | No (one req/resp) | Subscriptions |
| Browser | Needs grpc-web proxy | Native | Native |
| Sweet spot | Internal microservices | Public/simple APIs | Flexible client queries |
When to Use gRPC (and When Not To)
Reach for gRPC for internal, performance-sensitive, service-to-service communication — especially in a polyglot microservices system where strict contracts and small, fast payloads matter, or when you need streaming. It's a common choice for the east-west traffic behind an API gateway, which often translates external REST to internal gRPC. Avoid it for public APIs (where consumers expect human-readable JSON and easy curl-ability) and for direct browser calls (browsers can't speak raw gRPC, so you need a grpc-web proxy). A frequent architecture: REST/GraphQL at the public edge, gRPC between services internally.
Limitations and Pitfalls
- Not human-readable — binary payloads can't be eyeballed or curl'd; debugging needs tooling (grpcurl, reflection).
- Browser support — requires grpc-web + a proxy; not a drop-in for frontend calls.
- Smaller public ecosystem — REST's tooling/caching/docs ecosystem is broader for open APIs.
- Proto management — sharing and versioning
.protofiles across teams needs discipline (a schema registry or shared repo). - HTTP caching — you lose the easy HTTP caching/CDN story that GETs over REST enjoy.
gRPC = Protocol Buffers (compact binary, schema'd, evolvable) + HTTP/2 (multiplexed, streaming) + contract-first codegen. That combination makes it fast, strongly typed across languages, and great for internal microservice traffic and streaming — while its binary, non-browser-native nature keeps REST/GraphQL on top for public and frontend-facing APIs. The pragmatic pattern is REST at the edge, gRPC inside.
What is gRPC? A contract-first RPC framework using Protocol Buffers over HTTP/2, with generated typed stubs so a remote call looks like a local method.
Why is it faster than REST/JSON? Protobuf is compact binary (tags not field names) and quick to parse, and HTTP/2 multiplexes many calls over one connection with header compression.
What are the four call types? Unary, server-streaming, client-streaming, and bidirectional streaming — enabled by HTTP/2 streams.
When NOT to use gRPC? Public APIs (want human-readable JSON) and direct browser calls (need a grpc-web proxy); use REST/GraphQL there.
How does it relate to protobuf schema evolution? Add fields with new tag numbers, never reuse or renumber tags — giving backward/forward compatibility (see encoding & evolution).