When one service calls another hundreds of times per second, REST's overhead starts to hurt: text JSON has to be serialized and parsed, headers repeat, and the contract holds "on trust" — on documentation that goes stale. gRPC solves exactly this: a strict contract, a compact binary format, and a fast transport. Let's see how it works and when to reach for it.
What gRPC is
gRPC is a way to call another service's method as if it were local: you write orders.Get(id), and under the hood a network request goes out. This approach is called RPC (remote procedure call). Google built gRPC on top of two things:
- protobuf (Protocol Buffers) — a contract description language and a compact binary data format;
- HTTP/2 — a transport with multiplexing and persistent connections (covered in the article on HTTP versions).
Unlike REST, where you think in terms of resources and URLs, in gRPC you think in terms of services and their methods.
The contract in protobuf
Everything starts with a .proto file — the single source of truth for the contract. It describes messages (data structures) and a service with methods:
syntax = "proto3";
message GetOrderRequest {
string id = 1;
}
message Order {
string id = 1;
string status = 2;
int64 amount = 3; // in minor units, e.g. cents
}
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
}
The numbers = 1, = 2 are not values but field numbers: they are what gets written into the binary format instead of names. That's why protobuf is more compact than JSON (no repeated field names) and stays compatible as it evolves: you can add a new field with a new number without breaking old clients.
Code generation: the contract becomes code
From the .proto a tool generates classes and stubs in your language — the server implements an interface, the client gets a ready-made call. The rule is simple: the .proto is the source of truth, and code is generated from it, not written by hand. This is the key difference from REST: the contract is checked by the compiler. A typo in a field name is caught at build time, not in production.
Four kinds of calls
gRPC does more than "request-response". There are four modes, thanks to HTTP/2 streaming:
- Unary — one request, one response. Like an ordinary method call.
- Server streaming — one request, a stream of responses. For example, "subscribe to order updates".
- Client streaming — a stream of requests, one response. For example, uploading data in chunks.
- Bidirectional streaming — both ends send streams at once. For example, a chat or telemetry.
Streaming is something you have to improvise in REST (long-polling, WebSocket, SSE), while in gRPC it's built in.
Where this applies
gRPC shines in internal communication between services, where both sides are yours and speed and a strict contract matter:
- Microservices that call each other often: the binary format and reused HTTP/2 connections save time (on the cost of setting up connections, see the article on connections and pools).
- Strictly typed contracts between teams: the
.protois a shared language, and code generation keeps them from drifting apart. - Streaming scenarios: telemetry, subscriptions, event exchange.
Where gRPC is a poor choice:
- A public API for browsers and third-party developers. A browser can't do gRPC directly (you need a grpc-web proxy), and external consumers are more comfortable with REST + JSON, which you can read with your eyes and poke with curl.
- Ad-hoc debugging. The binary format can't be read in logs without tools; a REST response is readable immediately.
- HTTP caching. A REST
GETis cached by proxies and CDNs out of the box; a gRPC call is not.
Where beginners stumble:
- They drag gRPC into a public API for "speed", getting grief with browsers and external clients. For the public perimeter REST is almost always more appropriate.
- They change field numbers in the
.proto— that breaks compatibility. A field number is sacred; a field can only be added with a new number or marked deprecated. - They forget deadlines and retries. A fast call doesn't mean a reliable one — the network is still unreliable (see timeouts and retries).
- They put gRPC everywhere "because it's trendy", even when there are only a couple of calls per second inside — then the gain is invisible and only the complexity is added.
What to learn next
gRPC is one fork in designing a contract. Alongside it is GraphQL, which solves a different pain (flexible data slices for the client), and plain REST, which you should start from by default. gRPC's transport foundation is HTTP/2, and the reliability of calls between services is in the article on timeouts, retries, and idempotency. How the choice of style fits into designing a whole system is in the system design section.