0%
Networking for Backend Engineers
The Stack
Transport
Names and Trust
Getting Traffic to a Server
Performance
gRPC and Protobuf on the Wire
gRPC is not a faster REST. It is a different model of what a network call is, and the differences are worth understanding before the encoding details, because they explain every design decision that follows.
Resources against procedures
REST models a system as resources identified by URLs, manipulated with a small fixed set of methods. gRPC models it as services exposing procedures, each taking a typed request message and returning a typed response message.
That difference decides what is natural to express. "Create an order" is a resource creation and fits REST comfortably. "Recalculate this account's risk exposure" is a procedure, and expressing it in REST requires inventing a resource that does not exist in the domain, which is why mature REST APIs accumulate endpoints like /accounts/42/risk-recalculations.
Neither model is correct in general. REST's uniformity buys caching, intermediary understanding and a shared vocabulary of status codes. RPC's directness buys a closer fit to operations that are genuinely actions, and a much tighter coupling between the interface and the code that implements it.
Schema first, and what that actually changes
A gRPC service is defined in a .proto file that declares messages and service methods, and that file is the source of truth from which client and server code are generated.
The practical effect is that the interface cannot drift from the implementation. A field renamed in the schema breaks compilation on both sides, which is a build failure rather than a production surprise. With a hand-written JSON API the same change is a runtime discovery, usually by a client team.
The cost is that the schema becomes a shared artifact requiring coordination, versioning and distribution, which is organisational work that a JSON API lets you skip right up until it bites.
Generated code, and the illusion it creates
From the schema, gRPC generates message types with accessors, a client stub whose methods look like local function calls, and a server base class to implement.
The value is real: no hand-written serialisation, no hand-written HTTP paths, no mismatch between what the client sends and what the server expects. The risk is equally real, and it is the oldest problem in RPC: a call that looks like a function call is not one. It can be slow, it can fail, it can partially succeed, and it crosses a trust boundary.
Every mature gRPC codebase ends up making the remoteness visible again, through explicit deadlines on every call, explicit retry policy, circuit breaking, and error handling that distinguishes a network failure from an application error. Teams that let the local-call illusion stand write code that behaves badly in exactly the conditions distributed systems spend their time in.
Four kinds of call
gRPC defines four call shapes, and the three streaming ones are the capability REST does not have.
Unary is one request, one response, which is the vast majority of real traffic. Server streaming is one request and a stream of responses, which fits a subscription, a large result set, or a progress feed. Client streaming is a stream of requests and one response, which fits an upload or a batch accumulation. Bidirectional streaming is both streams running independently, which fits a long-lived conversation such as a chat, a telemetry channel or a control plane.
The important detail about bidirectional streaming is that the two directions are genuinely independent: the server may respond before the client has finished sending, and the application protocol decides the pattern. That is strictly more expressive than request-response and is the reason gRPC requires HTTP/2.
Why it needs HTTP/2 specifically
gRPC maps a call onto a single HTTP/2 stream, and it uses three HTTP/2 features that HTTP/1.1 does not have.
Streams give multiplexing, so many concurrent calls share one connection without head-of-line blocking at the HTTP layer, and they give cancellation, so abandoning one call does not disturb the others. Trailers carry the final status, which is essential because a streaming response cannot know its outcome until the stream ends. And flow control operates per stream, so one slow consumer does not stall the rest.
This is why gRPC cannot be translated to HTTP/1.1 by a proxy. It is not a performance preference, it is a structural dependency, and it is the reason a load balancer in front of a gRPC service must speak HTTP/2 to the backends.
Where the performance actually comes from
gRPC is faster than a typical JSON API, and it is worth being precise about why, because two of the three reasons have nothing to do with gRPC.
Protobuf encoding produces smaller payloads than JSON and parses faster, since there are no strings to scan, no numbers to parse from text and no schema inference. For small messages the difference is often three to ten times in size.
Connection reuse and multiplexing come from HTTP/2 and would help any protocol. Header compression likewise. A JSON API over HTTP/2 with a warm connection pool captures a good share of gRPC's advantage.
The third reason is the generated code path: no reflection, no dynamic dispatch over field names, no intermediate map representation. For a service handling high message rates this can dominate, and it is the part hardest to replicate with hand-written JSON handling.
What you give up
Four losses are worth weighing explicitly, because they are the reason gRPC is not the default everywhere.
Human readability goes first. A protobuf payload in a log or a capture is bytes, and without the schema it cannot be fully decoded, which changes debugging from reading to tooling.
Browser support is limited. Browsers cannot control HTTP/2 framing or read trailers, so gRPC-Web with a proxy translation layer is required, and it does not support client streaming.
HTTP caching disappears entirely, since every call is a POST to a method path with a binary body. Any caching has to be built inside the application.
And the ecosystem of HTTP tooling becomes less useful: curl does not speak it, a load balancer cannot route on a field, and a WAF cannot inspect a body it cannot parse.
When REST is the better answer
A short decision rule holds up well. Use gRPC for internal service-to-service traffic, where both sides are yours, the schema can be shared, performance matters and streaming is useful.
Use REST or plain JSON over HTTP for public APIs, where third-party developers need discoverability and curl; for browser-facing endpoints, where gRPC-Web's translation layer is a cost with little benefit; where HTTP caching is valuable, such as read-heavy content; and where webhooks or integrations require something any language can call without generated code.
Plenty of systems run both, with gRPC inside and a REST gateway at the edge generated from the same schema, which is a reasonable arrangement and the one the grpc-gateway tooling exists to support.
The organisational prerequisite
The part of adopting gRPC that goes wrong is rarely technical, and it is worth naming before the technical sections.
Schemas have to live somewhere both teams can reach, with a review process, a versioning convention and a build that regenerates code. Without that, each team copies the .proto file, the copies diverge, and the compile-time safety that justified the choice silently disappears.
The arrangements that work are a shared schema repository with generated artifacts published per language, or a monorepo where the schema and all its consumers build together. Both require someone to own the tooling. A team adopting gRPC without deciding this is adopting the costs and postponing the benefit.
Reading a service definition quickly
Given an unfamiliar .proto file, a few questions extract most of what matters in a minute.
Which methods stream, and in which direction, since that decides the operational shape of every call. Whether the package name carries a version, which tells you how breaking changes are handled here. Whether field numbers have gaps marked reserved, which tells you the schema has been maintained rather than rewritten. Whether any enum lacks an explicit unspecified zero value, which predicts a class of bug. And whether optional or wrapper types appear, which tells you whether the authors thought about presence.
Those five checks catch most of the design problems a schema can have, and they are faster than reading the generated code.
A senior answer to why gRPC is faster separates the three sources. Protobuf's binary encoding gives smaller payloads and faster parsing with no text scanning or schema inference. HTTP/2 gives connection reuse, multiplexing and header compression, all of which a JSON API over HTTP/2 also gets. Generated code removes reflection and intermediate map representations from the hot path. Attributing the whole gain to protobuf is the common imprecision, and it leads teams to adopt gRPC when connection pooling and HTTP/2 on their existing JSON API would have captured most of the benefit for far less change.