BuildDoing the work

gRPC vs REST in 2026 — When to Pick Which

Practical decision criteria, not benchmark slides — the factors that actually change the answer

Same pad. Two pipes. Welding them is the bug. Style reference: photo by Rak Ankong on Unsplash.
Same pad. Two pipes. Welding them is the bug. Style reference: photo by Rak Ankong on Unsplash.

Most backend engineers can recite the comparison. REST is JSON over HTTP. gRPC is protobuf over HTTP/2. One is "simple." The other is "fast." The slide has a latency column and a winner.

Fewer people walk into the design review with the thing that actually changes the answer: who calls this on Monday, and whether that caller can read trailers.

The bake-off is not wrong. It is incomplete. Binary payloads and multiplexed streams are real. They rarely decide whether your public checkout stays POST /v1/checkout or your ledger-to-risk path becomes a generated stub.

The Bake-Off Slide Is Not the Decision

You already know the brochure. gRPC gives you four call shapes — unary, server stream, client stream, bidirectional — and a compiler that turns a .proto into stubs.

HTTP gives you methods and status classes that every language already ships a client for.

That is the part everyone agrees on. The fight starts when someone pastes a "gRPC is 7x faster" chart into the RFC and treats the protocol as the architecture.

You might be tempted to pick the faster column and move on. Do that only if the faster column's clients exist. A protobuf win that requires an Envoy hop, a generated TypeScript client, and a partner who asked for curl is not a win. It is a translation layer you now own.

The useful artifact is not a feature matrix. It is the short list of conditions that flip the pick. Call them Flip Factors. Same discipline as picking a workload contract instead of a database logo.

Flip Factors: Consumer, Shape, Edge, Errors

Four questions. If you cannot answer them, you are still on the slide.

Consumer. Who is on the other end — a service you compile, a mobile SDK you ship, a browser tab, or a stranger with requests?

If you own both ends and the callers will take generated stubs, RPC is a fit. If other teams or partners integrate without learning your procedure set, HTTP JSON is the contract they already know.

Mobile sits in the middle. A first-party iOS client can take a stub. A white-label partner SDK that three banks embed cannot. The question is still who compiles your .proto on their CI.

The browser is not a gRPC client. There is no browser API with enough control over HTTP/2 frames to speak the native spec. You cannot call gRPC from a browser without extra machinery.

gRPC-Web exists. It needs a proxy — Envoy is the default — and it does not give you client or bidirectional streaming. That is a translation layer, not a public-API default.

Shape. Is this POST plus a body, or a stream that stays open?

Unary gRPC and REST look similar from a distance. Streaming is the clean flip. A server stream of settlement events, or a bidirectional risk feed, is what the four method kinds are for. If the job is create-a-checkout and return 201, you do not need a stream. You need a URL a runbook can hit.

Edge. Does every hop to this service speak HTTP/2 end to end?

gRPC's wire is HTTP/2 frames, te: trailers, and application/grpc. A load balancer that terminates HTTP/1.1 and "forwards gRPC" is not gRPC with extra latency. It is a translator. If your path still lives on HTTP/1.1 JSON at the public edge — CDN, partner, browser — REST is not nostalgia. It is the path that already works.

Errors. What does the caller already know how to handle?

HTTP clients speak 4xx and 5xx. That surface is already a client contract — status codes, retries, what on-call greps.

gRPC returns a status object — integer code plus description — and puts grpc-status in trailers, often behind an HTTP 200. Your on-call already greps status codes. Your new stub greps NOT_FOUND. Mixing those without naming the surface is how a 200 becomes a three-hour incident.

Protobuf is smaller and generates bindings. It is also not self-describing without the schema. That is a feature when both sides compile the same .proto. It is a tax when the consumer is a support engineer pasting a body into a ticket.

Payments: One Org, Two Wires

Same company. Two correct answers.

Public checkout stays REST. POST /v1/checkout with a JSON body. Mobile, a partner, a browser, a Zapier test, the on-call curl from a runbook. The consumer is not a generated stub. The shape is unary. The edge is the public internet. The error model is 402 / 409 / 422 — codes the client's HTTP library already maps.

Internal settlement is a different wire. Ledger streams SettleBatch events to risk. Ordering matters. The feed stays open. Both sides are services you compile. Here gRPC's server stream is the product, not a performance hobby. The stub is the client. Trailers are the error path, and that is fine because nobody is grepping nginx logs for this hop.

Week one, someone will ask why you "didn't standardize." Because the consumers are different. Standardizing would mean either grpc-web in front of checkout — a proxy, a generated JS client, no bidirection, a partner who cannot POST JSON — or REST polling on the risk feed because "we don't do gRPC here."

Two wires is not indecision. It is the Flip Factors applied twice.

Org-Wide Protocol: The Mandate That Fails in Week Two

The guild slide says: all new services are gRPC. It sounds like architecture. It is a consumer assumption wearing a standard.

Anticipate the defense: "We will expose REST at the edge and keep gRPC inside." That can work. It is also a second protocol you operate — transcoding, a facade, two error models, two runbooks. Name the facade as a product. Do not pretend the mandate was free.

A named facade has an owner, a version, and a deprecation rule. An accidental facade is the JSON you bolted on Friday because the partner demo is Monday. One of those belongs in the architecture diagram. The other belongs in the postmortem.

The week-two death is specific. Platform ships CheckoutService. Partner integration is a Python one-liner and a webhook. They do not have protoc. They have requests. You either add a JSON facade in a hurry or you lose the integration. The mandate did not make you consistent. It made the public contract an afterthought.

The inverse mandate — "we don't do gRPC" — fails the other way. You keep REST on a bidirectional feed and invent chunked polling, then write a design doc about why your queue looks like a stream.

Org-Wide Protocol is the anti-pattern. Pick per surface.

What This Lookup Will Not Pick

This is not a latency table. If you need numbers, measure your checkout and your stream on your mesh. Other people's RPS slides are someone else's cluster.

This is not a protobuf tutorial. The compiler is a Flip Factor, not a chapter.

GraphQL, tRPC, and Connect are other answers to other questions. They do not belong in this title. If your client is a browser and you already live in HTTP JSON, you are still on the REST side of this lookup until a stream or a stub actually shows up.

It will not write your ingress YAML. If a hop cannot carry HTTP/2 and trailers, you do not have gRPC on that hop. You have a project.


Name the consumer. Name the shape. Refuse the mandate.

Related

LeadDoing

The Rewrite Decision

Whether you rewrite or refactor is a bet you're asking a room to fund — and most of us pitch it like it's a matter of taste.

8 min · July 3, 2026

← Back to hub