gRPC in Go: Contracts, Streaming, and Performance
Microservices need a contract that survives refactors, a wire format that stays compact, and a transport that multiplexes many calls on one connection.
Search across all documentation pages
Microservices need a contract that survives refactors, a wire format that stays compact, and a transport that multiplexes many calls on one connection.
gRPC answers all three by pairing Protocol Buffers schemas with HTTP/2 framing and a small set of RPC patterns.
Go's google.golang.org/grpc package is the de facto server and client stack for internal platforms.
gRPC Basics walks runnable setup; sibling pages cover schema design, streaming, interceptors, errors, gateways, and operations.
.proto files define services and messages, code generators emit typed Go APIs, and HTTP/2 carries length-prefixed protobuf frames between processes.Think of gRPC as "functions across the network."
You declare a service in a .proto file:
service Inventory {
rpc GetStock(GetStockRequest) returns (GetStockResponse);
}protoc with protoc-gen-go and protoc-gen-go-grpc emits Go interfaces such as InventoryServer and client stubs.
The server implements the interface; the client calls client.GetStock(ctx, req).
Under the hood, each call becomes an HTTP/2 stream carrying protobuf-encoded request and response messages.
Unlike REST, the path and verb are fixed by the RPC name - you do not design URLs per endpoint.
Unary RPCs are request-response, like a function call.
Server streaming sends many responses to one request (tail logs, search results).
Client streaming uploads many requests before one response (bulk ingest).
Bidirectional streaming keeps both sides open (chat, replication).
Metadata (key-value headers) travels outside the message body for auth tokens, trace IDs, and tenancy hints.
A typical Go server registers implementations on a grpc.Server, listens on TCP, and serves HTTP/2:
Client stub Server
│ │
│── HTTP/2 stream ───────►│ interceptor chain
│ (protobuf frames) │ │
│ │ handler method
│◄── status + message ────│
Deadlines ride on context.Context.
When a client sets context.WithTimeout, the server observes ctx.Done() and should stop work.
Status returns as a gRPC code (codes.NotFound, codes.InvalidArgument, …) plus a message and optional details protobufs for structured errors.
The Go stack uses HTTP/2 flow control so one massive stream does not starve others on the same connection.
Keepalive settings matter for NATs and L4 load balancers that silently drop idle TCP.
Performance wins versus JSON REST come from smaller payloads, no repeated HTTP headers per call (HPACK), and optional gzip compression for large messages.
Profiling still matters: reflection, allocation-heavy protobuf merges, and interceptor chains add overhead.
| Approach | Strength | Weakness | Best Fit |
|---|---|---|---|
| gRPC + protobuf | Strong contracts, fast binary wire | Needs codegen pipeline | Internal microservices |
| REST + JSON | Universal tooling, easy debugging | Weak typing, larger payloads | Public HTTP APIs, browsers |
| gRPC-Gateway | One proto, JSON clients | Extra hop, mapping limits | Mobile/BFF layers |
| Connect/gRPC-Web | Browser-friendly variants | Ecosystem fragmentation | SPA talking to gRPC backends |
| Message queues | Decoupled async | Higher latency, ordering complexity | Fire-and-forget events |
In Kubernetes, headless Services or L7 proxies (Envoy, Linkerd, Istio) route gRPC by HTTP/2 authority and :path.
L4 balancers that rotate TCP per request break streaming unless they are gRPC-aware.
mTLS and JWT metadata are standard patterns; interceptors centralize auth before handlers run.
OpenTelemetry gRPC stats handlers emit spans per RPC; pair with metadata propagation (traceparent).
Proto versioning (v1, v2 packages) lets fleets migrate without flag days.
Breaking changes (renumbering fields, changing types) are deployment hazards - treat .proto like database migrations.
controller-runtime and operator ecosystems increasingly expose gRPC alongside REST for admission and extension APIs.
For public edges, teams front gRPC services with grpc-gateway or BFF layers that speak JSON while core services stay protobuf-native.
Watch GOMAXPROCS, connection pool sizing, and max concurrent streams when one client fans out to thousands of backends.
io.EOF and error trailers correctly.Strong API contracts via protobuf, first-class streaming, and efficient HTTP/2 multiplexing.
REST remains better for ad hoc HTTP caching and browser-first public APIs.
gRPC handlers implement generated interfaces, not http.HandlerFunc.
You can mount gRPC and REST on different ports or use gateways to expose both.
Add fields with new numbers; never reuse numbers.
Publish new packages (v2) for incompatible changes and run both during migration.
On context.Context created by the client.
Servers should respect ctx.Done() and return codes.Canceled or codes.DeadlineExceeded.
gRPC's canonical IDL is protobuf.
Other serializers exist in other ecosystems; Go production stacks assume protobuf.
Log status codes, enable grpc debug logging, and use grpcurl or BloomRPC against reflection-enabled servers.
Structured error details beat plain text messages.
When the server produces an unbounded sequence, the client uploads chunks, or both sides need concurrent writes.
Unary stays simpler for CRUD.
No - queues decouple producers and consumers in time.
gRPC suits synchronous request-response and controlled streams between known peers.
Long-lived HTTP/2 connections and bidi streams need connection-aware routing or proxyless service mesh configs.
gRPC sits on HTTP/2 but not on ServeMux patterns.
Many teams run chi/gin/echo for public HTTP and gRPC for internal RPC on separate listeners.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026