Most “gRPC vs REST” articles pick a side. gRPC is the future. REST is simpler. The reality is more nuanced: the right choice depends on who calls the API, how often, and what the consumers can actually debug at 2 AM. This article compares the two protocols side by side and walks through three production cases where the choice was not obvious from the start.
The numbers side by side
The table below shows benchmark results from a controlled test — same payload, same server hardware, same network. The numbers are typical of what teams see in production, not theoretical ceilings.
| Metric | REST (JSON over HTTP/1.1) | gRPC (Protobuf over HTTP/2) |
|---|---|---|
| Payload size (avg) | 1,247 bytes | 312 bytes |
| Avg response time | 87 ms | 34 ms |
| P99 latency | 210 ms | 62 ms |
| Throughput (req/s per node) | 1,200 | 4,800 |
| Connection model | New connection per request (or small pool) | Multiplexed over single HTTP/2 connection |
| Streaming | Bolted on (chunked, WebSockets) | Native (unary, server, client, bidirectional) |
| Type safety | Optional (OpenAPI) | Enforced (proto schema) |
| Browser support | Native | Requires grpc-web proxy |
| Debugging tooling | curl, browser DevTools, Postman | grpcurl, custom clients, proto file required |
| Code generation | Optional | Standard (12+ official languages) |
The payload size difference is the biggest lever. JSON carries field names in every message. Protobuf carries field numbers. For a service that handles 10,000 requests per minute, the size difference compounds into meaningful bandwidth and CPU savings.
The numbers tell one side of the story. The other side is who calls the API and what happens when it breaks.
Case 1 — Internal ERP service: gRPC migration paid off
A mid-sized manufacturing company ran an internal ERP service over REST. The service exposed 38 endpoints to other internal services: inventory, orders, shipping, payroll. Total traffic: roughly 50,000 requests per minute during business hours.
Symptoms that triggered a protocol review:
– P99 latency for the order-lookup endpoint was 180 ms during peak hours. – Each request opened a new HTTP connection (HTTP/1.1 keep-alive was disabled by an intermediate proxy). – CPU usage on the API servers sat at 70% during business hours, mostly on JSON serialization.
The team migrated the internal-only endpoints to gRPC over a 3-week period. The migration kept REST for two endpoints called by external partners.
Results after 6 months in production:
– P99 latency on order-lookup: 180 ms → 54 ms. – CPU usage on the same hardware: 70% → 35%. – Connection count: 50,000 open sockets → 200 persistent HTTP/2 connections. – Type mismatches between services: 4-6 per quarter → zero.
The catch: every external integration broke during the migration. Two payment gateway webhooks and one tax-reporting API had to be re-wired through a small grpc-gateway proxy. The proxy added 8 ms of latency, which was acceptable for the non-critical paths.
When this pattern fits: high-throughput internal traffic, stable consumer set, latency-sensitive SLAs.
Case 2 — Public payment API: REST was the right default
A fintech startup built a public API for payment processing. The API was called by three groups: the company’s own web frontend, a mobile app, and roughly 80 third-party merchants integrating via webhook callbacks.
The team considered gRPC during the design phase. The decision: REST, with no gRPC anywhere on the public surface.
Reasons:
– Debugging speed matters most when consumers are strangers. A merchant integrating at 2 AM needs to run curl -X POST https://api.example.com/v1/charge -d '{...}' and see the error in 30 seconds. With gRPC, that same merchant needs the proto file, the generated client, and a way to authenticate against the dev environment — none of which ship with their laptop. – Browser support is non-negotiable. The web frontend calls the API directly. gRPC-web exists, but it is a proxy layer, not native browser support, and the team’s web engineers were not interested in maintaining a translation layer. – Every language has an HTTP client. The merchant integrations included Python, PHP, Ruby, Java, and two implementations in COBOL. REST works with all of them out of the box. gRPC has official support for 12 languages; everything else requires a custom client or a translation shim. – Webhook receivers are universal REST. Sending a webhook to a merchant means POSTing JSON. If the inbound payment API is gRPC, the webhook handler still has to be REST. Two protocols for one product surface doubles the testing surface.
When this pattern fits: public APIs, browser clients, third-party integrations, low-volume internal traffic.
Case 3 — Real-time inventory sync: gRPC streaming replaced polling
A logistics company ran an inventory sync service that pushed stock-level changes to 14 downstream services (warehouse dashboards, e-commerce site, ERP, fulfillment partners).
The original implementation polled every 30 seconds:
GET /inventory → compare to last snapshot → POST /inventory if changed
At 14 consumers polling every 30 seconds, that was 28 requests per minute per consumer, or roughly 400 requests per minute across the fleet. Most polls returned “no change” — pure overhead.
The team rewrote the service with gRPC server streaming. The proto file declared one endpoint that opens a stream and pushes updates as they happen:
rpc SubscribeInventory(InventoryRequest) returns (stream InventoryUpdate);
Results after 3 months:
– 400 polling requests per minute → 14 persistent streams. – Inventory accuracy at downstream consumers: ±30 seconds → ±2 seconds. – Network bytes for “no change” notifications: ~all of them → zero. – Operational cost of the sync service: dropped by roughly 40% (less HTTP overhead, fewer wakeups).
The catch: gRPC streaming is not visible in browser DevTools. When something stalls, the on-call engineer has to use grpcurl or a custom debugging client. The team wrote a small CLI tool that opens a stream and prints the messages, which lives in the team’s runbook.
When this pattern fits: event-driven architectures, real-time data pipelines, anything where polling is the obvious-but-wrong default.
The decision framework
| If your API is consumed by… | Default to | Why |
|---|---|---|
| Browsers | REST | Native browser support, universal tooling |
| Mobile apps | REST | Easier debugging, fewer client dependencies |
| Third-party developers | REST | curl + docs is the universal onboarding flow |
| Webhook receivers | REST | Webhooks are REST by convention |
| Internal services you control | gRPC | Latency, payload size, type safety |
| High-throughput service mesh | gRPC | HTTP/2 multiplexing, persistent connections |
| Real-time data pipelines | gRPC streaming | Native bidirectional streaming |
| Long-running RPC chains | gRPC | Strong typing catches mismatches at compile time |
The framework is not a rule. The hybrid pattern is the most common outcome in practice: REST on the public surface, gRPC for internal service-to-service traffic, and a translation layer (grpc-gateway or a custom proxy) where the two need to meet.
What tends to break during a REST-to-gRPC migration
Three categories of work appear in every migration plan that gets underestimated.
1. Load balancer health checks. Most load balancers are configured to hit GET /health on a fixed interval. gRPC does not have URL paths — it has service methods. The health check starts returning 404 because /health does not exist in the gRPC world. The fix is a separate HTTP health endpoint on a different port, just for the load balancer. Two ports, one service, forever.
2. Logging and tracing. REST logs are human-readable: POST /api/v1/orders 200 87ms. gRPC logs are method names: /orders.v1.OrderService/CreateOrder 0 34ms. The format works for machines, but it is rough for the engineer debugging a production issue at 2 AM. A translation layer in the logging pipeline that maps method names back to human-readable descriptions is worth the day’s work to set up.
3. Authentication middleware. REST auth typically lives in HTTP headers or a JWT in the Authorization header. gRPC uses metadata, not headers. The middleware that worked for REST does not work for gRPC without a rewrite. Auth interceptors are small in terms of code, but the security review takes longer than the implementation.
These are not gRPC problems. They are migration problems. They add up. Budget a week for the migration, not a weekend.
The takeaway
gRPC vs REST is not a religious debate. It is an engineering decision with clear trade-offs. If the consumers are diverse and debugging speed matters, REST is the right default. If the consumers are internal services and latency matters, gRPC pays for itself in weeks.
The hybrid pattern — REST for external, gRPC for internal, grpc-gateway as the bridge — gives both without doubling the codebase. The general rule: start with REST, add gRPC where the numbers justify it, and avoid migrating a working REST API to gRPC without a specific number trying to be improved. “gRPC is faster” is not a reason. “P99 latency is 210 ms and the SLA requires 100 ms” is a reason.
If you are choosing a protocol for a new service, start with the consumer question: who calls this API, and what tooling do they have on their laptop at 2 AM? The answer picks the protocol for you.
Read more: Why I Ditched Docker Compose for Bare-Metal ERP Staging and Confluent & Kafka: From Microservices to AI Data Streams.
Discover more from Susiloharjo
Subscribe to get the latest posts sent to your email.