Migration & Upgrades

Migrating from REST to gRPC

A practical guide to migrating APIs from REST/JSON to gRPC/Protobuf — covering when to migrate, schema design, the gRPC-Gateway pattern, error mapping, and gradual rollout strategies.

When to Consider gRPC

gRPC is not a replacement for REST — it is a tool optimized for specific use cases. Consider migrating when:

  • High throughput, low latency — microservice-to-microservice calls where JSON parsing overhead is measurable
  • Strongly typed contracts — Protobuf .proto files as the source of truth, shared across languages
  • Bidirectional streaming — chat, real-time analytics, telemetry pipelines
  • Polyglot services — multiple languages generated from one .proto

Keep REST when: you need browser clients, public APIs, simple CRUD endpoints, or your team lacks gRPC experience. gRPC adds complexity — earn it.

REST vs gRPC Comparison

AttributeRESTgRPC
ProtocolHTTP/1.1 or HTTP/2HTTP/2 only
Payload formatJSON (text)Protobuf (binary)
SchemaOpenAPI (optional)`.proto` (required)
Browser supportNativegrpc-web proxy required
StreamingSSE / WebSocketBuilt-in (4 modes)
Code generationOptionalFirst-class (`protoc`)
Payload size2–5× larger vs ProtobufSmallest
LatencyHigher (JSON parse)Lower
ObservabilityStandard HTTP toolsRequires gRPC tooling

Protobuf serialization is typically 5–10× faster than JSON and produces 60–70% smaller payloads, making it the primary motivation for gRPC in high-throughput microservices.

Protobuf Schema Design

Design .proto schemas carefully — Protobuf is more strict about backward compatibility than JSON.

syntax = "proto3";
package api.v1;

service ProductService {
  rpc GetProduct (GetProductRequest) returns (Product);
  rpc ListProducts (ListProductsRequest) returns (ListProductsResponse);
  rpc CreateProduct (CreateProductRequest) returns (Product);
}

message Product {
  int64 id = 1;
  string name = 2;
  double price = 3;
  string category = 4;
  // Field 5 deleted — never reuse field numbers!
  google.protobuf.Timestamp created_at = 6;
}

Critical rules:

  • Never reuse field numbers — old clients still decode deleted fields using the old type. Reserved deleted numbers: reserved 5;
  • Never change field typesint32 to int64 is wire-incompatible
  • Use optional for new fields — ensures old servers handle missing fields gracefully
  • Version your package (api.v1, api.v2) for major breaking changes

gRPC Gateway (REST + gRPC)

The grpc-gateway project generates a reverse proxy from .proto annotations that exposes your gRPC service as a REST/JSON API:

import "google/api/annotations.proto";

service ProductService {
  rpc GetProduct (GetProductRequest) returns (Product) {
    option (google.api.http) = {
      get: "/v1/products/{id}"
    };
  };
}

This generates a REST gateway that translates:

GET /v1/products/42 → gRPC GetProduct(id: 42)

The gRPC-Gateway pattern lets you run REST and gRPC simultaneously from the same service — REST for browser clients and public APIs, gRPC for internal microservices.

Service Migration Strategy

Never do a big-bang migration. Use a strangler fig approach:

Phase 1: Add gRPC alongside REST (0% gRPC traffic)

  • Implement .proto schema for the target service
  • Run gRPC server on port 50051, REST on 8000
  • Generate client stubs for internal callers
  • Write integration tests for both interfaces

Phase 2: Migrate internal callers (20–80% gRPC traffic)

  • Update microservices to use gRPC one by one
  • Monitor error rates and latency in parallel
  • Roll back per-service if issues arise

Phase 3: Deprecate REST for internal use (100% gRPC internally)

  • Keep REST via gRPC-Gateway for external clients
  • Add deprecation warnings to REST responses
  • Remove internal REST call sites

Error Code Mapping

Map REST HTTP status codes to gRPC status codes systematically:

HTTP StatusgRPC StatusCode
200 OKOK0
400 Bad RequestINVALID_ARGUMENT3
401 UnauthorizedUNAUTHENTICATED16
403 ForbiddenPERMISSION_DENIED7
404 Not FoundNOT_FOUND5
409 ConflictALREADY_EXISTS6
429 Too Many RequestsRESOURCE_EXHAUSTED8
499 Client Closed RequestCANCELLED1
500 Internal Server ErrorINTERNAL13
503 Service UnavailableUNAVAILABLE14
504 Gateway TimeoutDEADLINE_EXCEEDED4

gRPC also supports rich error details via google.rpc.Status with structured metadata (field violations, retry info, debug messages).

Performance Benchmarks

Measure before and after migration with realistic payloads:

# ghz — gRPC benchmarking tool
ghz --proto api.proto \
    --call api.v1.ProductService/ListProducts \
    --data '{"page_size": 20}' \
    --concurrency 50 \
    --total 10000 \
    grpc-api.example.com:50051

Typical improvements in microservice benchmarks:

  • Throughput: 2–5× higher requests/second
  • P99 latency: 30–60% lower
  • Payload size: 60–70% smaller
  • CPU usage: 10–20% lower (less JSON parsing)

Gradual Rollout

Use feature flags or traffic-splitting to incrementally shift load:

# Python: feature flag controlling gRPC vs REST for a service call
def get_product(product_id: int) -> Product:
    if feature_flag('use_grpc_product_service'):
        return grpc_client.GetProduct(GetProductRequest(id=product_id))
    else:
        return rest_client.get(f'/v1/products/{product_id}')

Start with 5% of internal traffic, monitor error rates and latency percentiles, then increase to 25%, 50%, 100% over 1–2 weeks. Keep the REST implementation until gRPC proves stable in production.

Related Protocols

Related Glossary Terms

More in Migration & Upgrades