gRPC vs REST — Unbounded Stream Buffers Cause OOM
gRPC streaming caused 4GB heap OOM every 6 hours from unbounded buffers in production.
- gRPC uses Protocol Buffers (binary) over HTTP/2; REST uses JSON over HTTP/1.1 or HTTP/2
- gRPC supports four streaming patterns natively; REST needs workarounds like WebSockets
- gRPC is 2-5x faster for frequent small-payload calls; REST is baseline
- gRPC requires code-generated clients; REST works with any HTTP client
- Biggest mistake: using gRPC for public APIs without a REST gateway
I've built and maintained both REST and gRPC APIs in production. The decision is rarely about technical superiority — both work. It's about the consumers and their requirements. External API consumed by customers? REST. Internal microservice-to-microservice communication that processes 50,000 RPCs per second? gRPC. The mistake I see is teams defaulting to one or the other without asking 'who is consuming this and what do they need?'
In 2022, we migrated an internal order-processing service from REST to gRPC. The service handled inter-service communication between six microservices, averaging 30,000 requests per minute. After migration: 40% reduction in serialisation overhead, 60% reduction in request latency at P99. The consumer apps stayed on REST. The internal fabric went gRPC. Both in production, each doing what it's good at.
Protocol and Serialisation: Where the Performance Difference Comes From
REST typically sends JSON over HTTP/1.1. JSON is text — human readable, but verbose. Every field name is repeated as a string in every message. HTTP/1.1 opens a new TCP connection per request (or reuses one with keep-alive, but still has head-of-line blocking).
gRPC uses Protocol Buffers (protobuf) over HTTP/2. Protobuf is binary — fields are identified by integer tags, not string names. The same data that takes 200 bytes as JSON might take 40 bytes as protobuf. HTTP/2 multiplexes multiple requests over a single TCP connection and supports full-duplex streaming.
The performance difference is real but often overstated in benchmarks that don't reflect production conditions. The 3-5x latency improvement cited for gRPC over REST usually holds for high-frequency, small-payload inter-service calls. For large payloads or infrequent calls, the difference shrinks.
REST: Why It Still Wins for Most APIs
REST's dominance for public and external APIs isn't about technical merit — it's about ubiquity. Every HTTP client in every language speaks REST. Browsers speak REST natively. curl speaks REST. Postman speaks REST. Your customers' mobile app developers know REST. Your partners' integration teams know REST.
gRPC requires a generated client from the .proto definition. Your API consumers need to run protoc, add gRPC dependencies, and handle the generated code. For internal services under your control, this is a minor inconvenience. For external API consumers who might be using PHP, Ruby, or a legacy Java stack, it's a barrier.
REST also wins on tooling maturity: API gateways, load balancers, proxies, observability tools, and browsers all understand HTTP/JSON natively. gRPC on HTTP/2 requires specific support at every layer.
Streaming: The Capability REST Can't Match
gRPC's streaming support is its genuinely unique capability. REST over HTTP/1.1 is inherently request-response. Long polling, WebSockets, and Server-Sent Events are REST workarounds for streaming — they work but they're not native to the protocol.
gRPC has four communication patterns built into the protocol:
Unary: one request, one response. Same as REST.
Server streaming: one request, stream of responses. Useful for: real-time notifications, progress updates, large dataset pagination without polling.
Client streaming: stream of requests, one response. Useful for: bulk ingestion, file upload with progress tracking.
Bidirectional streaming: both sides stream simultaneously. Useful for: real-time chat, collaborative editing, live dashboards.
If your use case involves any of these patterns, gRPC's native streaming is significantly cleaner than working around REST's limitations.
Error Handling and Status Codes: Differences That Matter in Production
REST uses HTTP status codes: 200 for success, 400 for bad request, 404 for not found, 500 for server error. These are well-understood but limited. Custom error messages go in the response body and are not standardised across implementations.
gRPC defines a set of status codes in protobuf (like INVALID_ARGUMENT, NOT_FOUND, INTERNAL, UNAVAILABLE) that are standard across all implementations. Every gRPC response includes a status code and an optional error message. Additionally, gRPC supports rich error models through the google.rpc.Status proto, including error details with structured information.
The critical difference is that gRPC's error model is consistent across all client languages — the same status code means the same thing in Java, Go, or Python. REST depends entirely on documentation and convention.
Tooling and Ecosystem: Where REST Still Leads
REST has decades of tooling maturity. Postman, Insomnia, curl, and browser dev tools all understand HTTP/JSON natively. API gateways like Kong, AWS API Gateway, and Nginx handle REST without configuration. Monitoring tools parse HTTP methods, status codes, and response times out of the box. Load balancers distribute REST requests without special setup.
gRPC tooling has improved but still lags. grpcurl and BloomRPC exist, but they're not as popular as Postman. API gateways need explicit HTTP/2 support and gRPC-web translation for browsers. Monitoring gRPC calls requires special instrumentation: you need the service name, method, and status code — not just HTTP metadata. Some tools now support gRPC natively (like gRPC reflection for service discovery), but the gap is real.
If your team is small or you need to onboard junior engineers quickly, the REST toolchain gives everyone a head start. gRPC requires learning protoc, generated stubs, and a new debugging workflow.
| Aspect | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 or HTTP/2 | HTTP/2 only |
| Serialisation | JSON (text) | Protocol Buffers (binary) |
| Contract | OpenAPI/Swagger (optional) | .proto file (required) |
| Code generation | Optional | Required (client + server stubs) |
| Browser support | Native | Requires grpc-web proxy |
| Streaming | Workarounds (SSE, WebSocket) | Native (4 patterns) |
| Performance | Baseline | ~2-5x faster for frequent calls |
| Error handling | HTTP status codes + custom body | Standardised status codes + rich error model |
| Tooling | Universal (Postman, curl, etc.) | Specialised (BloomRPC, grpcurl) |
| Best for | Public APIs, external consumers | Internal microservices, high-frequency calls |
Key Takeaways
- REST over HTTP/JSON for external APIs, public-facing endpoints, and any consumer you don't control — ubiquity and tooling win.
- gRPC over protobuf/HTTP/2 for internal microservice communication, high-frequency calls, and when streaming is required.
- gRPC is 2-5x faster for frequent small-payload calls. For large payloads or infrequent calls, the difference is smaller.
- The .proto file is the source of truth for a gRPC API — it enforces a strict contract, enables code generation in any language, and prevents the schema drift common in JSON APIs.
- You don't have to choose: REST gateway for external traffic routing to internal gRPC services is a standard production pattern.
Common Mistakes to Avoid
- Using gRPC for a public API without a REST/JSON facade
Symptom: External developers cannot easily consume gRPC without generated clients. They complain about setup complexity. Some abandon the API entirely.
Fix: Provide a REST gateway that translates HTTP/JSON to protobuf. Deploy gRPC-Gateway or Envoy in front of the gRPC service. Offer both gRPC and REST endpoints documented side by side. - Choosing REST for high-frequency internal service calls because 'it's simpler'
Symptom: At 10,000+ RPCs per minute, CPU usage spikes due to JSON serialisation. P99 latency grows due to HTTP/1.1 connection overhead and head-of-line blocking.
Fix: Migrate to gRPC for internal services that exchange small payloads frequently. Use protobuf for the serialisation improvement and HTTP/2 for multiplexing. The migration effort pays off within weeks at that scale. - Not defining .proto contracts upfront for gRPC
Symptom: After a few field modifications, the .proto file no longer matches the actual data structure. Field numbers are reused or changed, causing silent data corruption. Clients break at runtime without obvious errors.
Fix: Treat the .proto file as the single source of truth. Never change field numbers or types after the contract is in use. Use reserved fields for retired fields. Enforce schema validation in CI to detect drift. - Ignoring gRPC's built-in deadline/timeout propagation
Symptom: A chain of microservices (A→B→C) where only A sets a deadline. B and C have no deadline context, so B waits indefinitely for C. A eventually times out, but B is stuck handling a request that A already gave up on. Cascading timeout storms follow.
Fix: Always propagate the gRPC deadline via the Context. UseContext.current().withDeadlineAfter(...)and pass it to downstream calls. Set a maximum deadline at the edge service. Never create a new Context without inheriting the deadline.
Interview Questions on This Topic
- QWhat are the main differences between gRPC and REST? When would you choose each?JuniorReveal
- QExplain how Protocol Buffers differ from JSON and what performance implications that has.Mid-levelReveal
- QWhat are the four communication patterns in gRPC and when would you use each?Mid-levelReveal
- QYou're designing a microservices architecture with 8 internal services and a public API. How would you decide which services use gRPC vs REST?SeniorReveal
- QWhat is gRPC-Gateway and why would you use it?SeniorReveal
Frequently Asked Questions
When should I use gRPC instead of REST?
Use gRPC for internal microservice-to-microservice communication where performance matters, when you need native streaming (server push, client streaming, bidirectional), or when strong schema contracts and code generation are important. Use REST for public APIs, external consumers, browser-facing services, or any situation where you can't control client code generation.
Is gRPC faster than REST?
Yes, typically 2-5x faster for frequent small-payload calls. The advantages come from Protocol Buffers (binary serialisation, smaller payloads than JSON) and HTTP/2 (multiplexing, header compression, no head-of-line blocking). For infrequent calls or large payloads, the performance gap narrows. The difference matters most at high request volumes — 10,000+ RPCs per minute.
Can gRPC and REST coexist in the same system?
Yes, and this is a common production pattern. A REST/JSON API gateway handles external traffic and translates to internal gRPC calls. External consumers get REST ergonomics and universal tooling. Internal services get gRPC performance and streaming. gRPC-Gateway and Envoy proxy both support this pattern out of the box.
Does gRPC work in browsers?
Not natively. Browsers cannot make HTTP/2 requests with the required trailers that gRPC uses. The solution is grpc-web, a modified protocol with a JavaScript client library, combined with an Envoy or Nginx proxy that translates grpc-web to grpc on the server side. For browser-facing APIs, REST remains the simpler choice.
What's the biggest pitfall when implementing gRPC streaming in production?
The biggest pitfall is forgetting backpressure. gRPC's flow control pauses transmission, but it doesn't stop the server from buffering messages in memory. If the client is slow, the server's outbound buffer grows until OOM. Always use bounded queues and set per-stream memory limits. Monitor heap per stream in development. Also be wary of deadlock: if two services use bidirectional streaming and each blocks on the other's response, you get a deadlock. Design for non-blocking processing.
That's Components. Mark it forged?
3 min read · try the examples if you haven't