What Are gRPC Interceptors
A gRPC interceptor is middleware that wraps every RPC call, letting you run shared logic — logging, authentication, metrics, retries — without touching individual handler code. Interceptors exist on both the client and server side, and each side supports two flavors: unary interceptors for single-request/single-response calls, and streaming interceptors for calls that involve a client stream, server stream, or bidirectional stream. Because they sit in the request path, interceptors can inspect and modify metadata, short-circuit a call before it reaches the handler, or wrap the response after it returns.
Cricket analogy: Like the third umpire reviewing every close run-out before the on-field decision stands, an interceptor reviews every RPC before it reaches the handler, whether it's Rohit Sharma's dismissal or a routine single.
Unary vs Streaming Interceptors
A UnaryServerInterceptor in Go has the signature func(ctx, req, info *UnaryServerInfo, handler UnaryHandler) (resp, error), and it must explicitly call handler(ctx, req) to continue the chain — omitting that call silently short-circuits the RPC. A StreamServerInterceptor instead wraps a ServerStream, typically via a wrapped struct that overrides SendMsg and RecvMsg so you can inspect or transform every message flowing through a long-lived stream, such as a bidirectional chat or a server-streaming price feed. When multiple interceptors are registered, they nest like layers, so grpc.ChainUnaryInterceptor(a, b, c) executes a's pre-handler logic, then b's, then c's, then the actual handler, then unwinds back through c, b, a for post-processing.
Cricket analogy: Like a DRS review chain where the on-field umpire's call, then the third umpire's replay, then Hawk-Eye's ball-tracking each add their verdict before a final decision on Steve Smith's LBW, chained interceptors each add logic before the handler runs.
func LoggingUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("method=%s duration=%s error=%v", info.FullMethod, time.Since(start), err)
return resp, err
}
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(RecoveryInterceptor, AuthInterceptor, LoggingUnaryInterceptor),
)Common Use Cases
The most common server-side interceptor use case is authentication: extracting a bearer token from incoming metadata, validating it against an identity provider, and injecting the resulting user claims into the context so downstream handlers can call ctx.Value(userKey) without re-parsing headers. Client-side interceptors are equally valuable for cross-cutting retry logic — wrapping a flaky call with exponential backoff on codes.Unavailable — and for injecting distributed tracing headers so a request's span ID propagates from an API gateway through five microservices without every handler author remembering to do it manually.
Cricket analogy: Like a stadium's centralized security screening that checks every spectator's ticket and bag regardless of which gate they enter through, an auth interceptor validates every incoming token regardless of which RPC method is called.
Interceptor execution order matters: with grpc.ChainUnaryInterceptor(a, b, c), interceptor a's pre-handler code runs first and its post-handler code runs last — so place recovery and tracing interceptors outermost (first in the list) and more specific logic like rate limiting closer to the handler.
Interceptor Chaining and Context Propagation
Because interceptors are typically chained in a fixed order — say, recovery first, then tracing, then auth, then rate limiting — a panic thrown deep in a handler will only be caught if a recovery interceptor sits outermost in that chain; place it last and an unhandled panic crashes the whole server process. Context propagation is the other subtlety: interceptors must pass along the same context.Context (or a derived one via context.WithValue) through every layer, because gRPC deadlines and cancellation signals are carried on that context, and dropping it anywhere in the chain silently disables timeout enforcement for the rest of the call.
Cricket analogy: Like placing the safety fielder at long-on last in a defensive field setting so any mis-hit six is still caught, placing the recovery interceptor outermost in the chain ensures a panic deep in the handler is still caught.
A panic inside a handler or a downstream interceptor will crash the entire gRPC server process unless a recovery interceptor (e.g. grpc_recovery.UnaryServerInterceptor()) is registered outermost in the chain to catch it and convert it into a codes.Internal error instead.
- Interceptors are gRPC middleware for cross-cutting concerns like logging, auth, metrics, and retries.
- Unary interceptors wrap a single request/response; stream interceptors wrap SendMsg/RecvMsg on long-lived streams.
- A unary interceptor must call handler(ctx, req) explicitly or the RPC never reaches the actual method.
- ChainUnaryInterceptor executes interceptors in registration order on the way in and unwinds in reverse on the way out.
- Place recovery interceptors outermost in the chain so panics anywhere downstream are caught.
- Client-side interceptors commonly implement retries with backoff and inject distributed tracing headers.
- Context propagation across every interceptor layer is required for deadlines and cancellation to work correctly.
Practice what you learned
1. What happens if a UnaryServerInterceptor in Go never calls handler(ctx, req)?
2. With grpc.ChainUnaryInterceptor(a, b, c), which interceptor's pre-handler logic runs first?
3. Why should a recovery interceptor be placed outermost in the chain?
4. What does a StreamServerInterceptor typically wrap to inspect messages on a long-lived stream?
5. What is a common purpose of a client-side gRPC interceptor?
Was this page helpful?
You May Also Like
Load Balancing in gRPC
Why gRPC's persistent, multiplexed HTTP/2 connections need client-side or proxy-aware load balancing, and how policies like round_robin and least_request work in practice.
gRPC Authentication with TLS
How gRPC uses TLS and mutual TLS to secure transport, and how call credentials layer token-based authorization on top for production services.
gRPC Reflection and grpcurl
How the gRPC Server Reflection service exposes a server's API at runtime, and how to use grpcurl to explore, describe, and invoke RPCs from the command line.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics