← Back to Blog

Why I Switched from REST to gRPC#

February 28, 2026 · 6 min read

For years, REST was my default choice for service-to-service communication. JSON over HTTP, easy to debug with curl, universally understood. Then our platform grew to 80+ internal services, and the cracks started to show. Latency was creeping up. Payload sizes were bloated. Schema drift between teams was causing silent bugs. Switching our internal APIs to gRPC cut our average inter-service latency by 40% and eliminated an entire class of integration bugs.

The Problem with REST at Scale

REST works beautifully for public APIs where human readability matters. But for internal service-to-service calls, JSON serialization is wasteful. A typical user profile response in JSON is around 1.2 KB. The same data encoded in Protocol Buffers is 340 bytes. When you are making millions of these calls per hour, that difference adds up fast — both in bandwidth and in serialization CPU time.

The bigger problem was schema management. With REST, your contract is a Swagger doc that may or may not reflect what the service actually returns. We had incidents where a team added a field to a response, another team started depending on it, and then the first team renamed it. No compile-time error. No warning. Just a null pointer in production at 3 AM.

Why Protocol Buffers Change Everything

With gRPC, your API contract lives in .proto files that are versioned alongside your code. Here is a simplified example:

syntax = "proto3";

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (stream User);
}

message GetUserRequest {
  string user_id = 1;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int64 created_at = 4;
}

From this single file, you generate strongly-typed client and server code in Go, Java, Python, or whatever your services use. If someone changes the schema in a backward-incompatible way, the consuming service fails to compile. That is a massive improvement over discovering the breakage in production.

We publish our proto files in a shared repository with CI checks that enforce backward compatibility using buf lint and buf breaking. No field can be removed or renumbered without a review.

Streaming: The Killer Feature

gRPC supports four communication patterns: unary (request-response), server streaming, client streaming, and bidirectional streaming. REST only gives you the first one natively. Server streaming replaced our polling-based notification system and reduced the load on our notification service by 60%.

For our real-time analytics dashboard, we use bidirectional streaming. The client sends filter updates, and the server pushes matching events as they arrive. Implementing this with REST would have required WebSockets, a separate connection management layer, and custom serialization. With gRPC, it was about 50 lines of code on each side.

The Numbers

We benchmarked our most critical path — the order creation flow that touches 6 services sequentially. Here are the results:

That is a 40% reduction in median latency and 45% at p99. The gains come from three places: binary serialization (faster than JSON parsing), HTTP/2 multiplexing (no head-of-line blocking), and persistent connections (no TCP handshake per request). The CPU savings on serialization alone freed up roughly 15% of our compute budget across the fleet.

When NOT to Use gRPC

gRPC is not a silver bullet. Here are cases where I still reach for REST:

Migration Strategy

We did not rewrite everything overnight. Instead, we followed a strangler fig pattern:

  1. Identified the 10 highest-traffic internal API paths using our service mesh telemetry.
  2. Defined proto files for those services first and published them to our shared proto repository.
  3. Ran both REST and gRPC endpoints in parallel for 4 weeks, comparing responses for parity.
  4. Switched consumers to gRPC one team at a time, with a feature flag for instant rollback.
  5. Decommissioned the REST endpoints after 30 days of zero traffic.

The entire migration for those 10 services took about 6 weeks with a team of three engineers. The remaining services were migrated opportunistically over the next two quarters.

Choose the right tool for the context. REST for the edges, gRPC for the internals. Your services will thank you with lower latency and fewer integration bugs.

If you are running more than a handful of internal services and are still on REST, I would strongly recommend prototyping gRPC on one high-traffic path. The performance gains and type safety alone make it worth the investment.