Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
09 Jul 2025 · 5 min read ·Article 31 / 110
Go

31 What Is an Interceptor in gRPC?

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

gRPC has become the backbone for migrating many monolithic systems toward microservices architectures. With its high performance, multi-language support, and ease of integration, it’s no surprise that gRPC is now widely used as the transport protocol between modern services. One powerful yet often underrated feature of gRPC is the Interceptor.

In this article, we’ll cover everything you need to know:

  • What an Interceptor is in gRPC
  • How it works under the hood
  • Usage examples (code)
  • A simulated case study
  • An execution flow diagram

Let’s get started!


What Is an Interceptor in gRPC?

Put simply, a gRPC Interceptor is much like the middleware found in web frameworks such as Express.js, Spring, or Gin. An Interceptor lets us “inject additional logic” in the form of cross-cutting concerns into the lifecycle of every RPC that gets processed.

Common cross-cutting concerns that are typically implemented through an Interceptor:

  • Logging
  • Authentication & Authorization
  • Metrics & Tracing (Distributed Tracing)
  • Exception Handling & Retry Logic
  • Rate Limiting

With an Interceptor, the code that handles the concerns above can be separated from the service’s core business logic, making the codebase cleaner, more maintainable, and more reusable.


Core Concepts of Interceptors in gRPC

Fundamentally, an Interceptor provides a hook into the execution of an RPC on both the client and the server side.

  • A Client Interceptor runs logic before the RPC is sent to the server, or when the response is received.
  • A Server Interceptor captures the request before it reaches the handler, or before the response is returned to the client.

Server-side Interceptors come in two forms:

  1. Unary Server Interceptor (for unary RPCs)
  2. Stream Server Interceptor (for streaming RPCs)

On the client side, the concept is the same.

Interceptor Flow Diagram

Let’s simulate the execution flow of a request passing through several server-side Interceptors using the mermaid diagram below:

MERMAID
flowchart LR
    Client(Request) --> |RPC Call| Interceptor1
    Interceptor1 --> Interceptor2
    Interceptor2 --> InterceptorN
    InterceptorN --> ServiceHandler
    ServiceHandler --> InterceptorN
    InterceptorN --> Interceptor2
    Interceptor2 --> Interceptor1
    Interceptor1 --> |Response| Client
In the diagram above:

  • Each Interceptor can run logic before forwarding the request to the next Interceptor or to the Service Handler (the handler’s core logic).
  • After the handler finishes, the Interceptor can also modify the response.

Implementing Interceptors: An Example with gRPC-Go

Let’s get hands-on using the Go language with gRPC-Go.

1. A Simple Unary Server Interceptor (Logging)

go
 1import (
 2    "context"
 3    "google.golang.org/grpc"
 4    "log"
 5    "time"
 6)
 7
 8// Unary Interceptor Function Type
 9func LoggingInterceptor(
10    ctx context.Context,
11    req interface{},
12    info *grpc.UnaryServerInfo,
13    handler grpc.UnaryHandler,
14) (resp interface{}, err error) {
15    start := time.Now()
16    log.Printf("gRPC method: %s; at: %v", info.FullMethod, start)
17    resp, err = handler(ctx, req) // call the next interceptor / actual handler
18    log.Printf("gRPC method: %s; finished in %v, err = %v", info.FullMethod, time.Since(start), err)
19    return resp, err
20}
How to register it with the server:
go
1grpcServer := grpc.NewServer(
2    grpc.UnaryInterceptor(LoggingInterceptor),
3)

2. Chaining Multiple Interceptors

gRPC v1.38+ supports chaining multiple interceptors using a helper:

go
1grpc.ChainUnaryInterceptor(
2    LoggingInterceptor,
3    AuthInterceptor,
4    TracingInterceptor,
5)
Each Interceptor is invoked in the order listed above.

3. Client Interceptor

Similar to the server side:

go
 1func ClientLoggingInterceptor(
 2    ctx context.Context,
 3    method string,
 4    req, reply interface{},
 5    cc *grpc.ClientConn,
 6    invoker grpc.UnaryInvoker,
 7    opts ...grpc.CallOption,
 8) error {
 9    log.Printf("Client calling: %s", method)
10    err := invoker(ctx, method, req, reply, cc, opts...)
11    log.Printf("Client finished: %s, err: %v", method, err)
12    return err
13}
14
15conn, err := grpc.Dial(
16    "localhost:50051",
17    grpc.WithUnaryInterceptor(ClientLoggingInterceptor),
18)


Case Study: Auth & Logging Interceptors

Let’s simulate implementing two Interceptors on a gRPC server: one for Authentication and another for Logging.

Auth Interceptor

go
 1func AuthInterceptor(
 2    ctx context.Context,
 3    req interface{},
 4    info *grpc.UnaryServerInfo,
 5    handler grpc.UnaryHandler,
 6) (interface{}, error) {
 7    md, ok := metadata.FromIncomingContext(ctx)
 8    if !ok || len(md["authorization"]) == 0 {
 9        return nil, status.Errorf(codes.Unauthenticated, "token missing")
10    }
11    token := md["authorization"][0]
12    if token != "valid-token" {
13        return nil, status.Errorf(codes.Unauthenticated, "invalid token")
14    }
15    // continue to the next handler
16    return handler(ctx, req)
17}

Combining Interceptors

go
1grpcServer := grpc.NewServer(
2    grpc.ChainUnaryInterceptor(
3        LoggingInterceptor,
4        AuthInterceptor,
5    ),
6)

Simulation Table

Request FlowLogging InterceptorAuth InterceptorHandler
No Authentication HeaderLogs requestFails, UnauthenticatedDoes not run
Wrong TokenLogs requestFails, UnauthenticatedDoes not run
Valid TokenLogs requestContinuesHandler runs
Error in HandlerLogs requestContinuesLogs Error

Advantages of Using Interceptors

  • Separation of Concern: The code for logging/auth/rate limiting doesn’t get mixed into the core handler.
  • Reusability: Interceptors can be reused across gRPC services.
  • Stackable/Composable: You can build an INTERCEPTOR pipeline tailored to your needs.

Best Practices & Tips

  1. Don’t carry local state in an Interceptor. Interceptors are invoked per-RPC, so prefer using the context and avoid any race conditions.
  2. Handle errors properly. Return errors with a gRPC status so they’re easy for the client to diagnose.
  3. Be careful when modifying the context. When needed, use context.WithValue and then pass it along.
  4. Streaming Interceptors differ from Unary ones. For streaming (server/client), the interceptor interface is different and requires extra care.

When Should You Use an Interceptor?

  • When you need to apply validation, authentication, metrics, and so on uniformly across ALL methods.
  • To keep your service’s business logic clean and free of other concerns.
  • For observability purposes: consistent logging/tracing.

Conclusion

The Interceptor is one of those gRPC features that gives engineers the power to keep a codebase clean, scalable, and observable. In modern microservices, building an ecosystem of cross-cutting concerns without Interceptors is like laying bricks without mortar: hard to read and prone to falling apart.

Is your gRPC service already taking advantage of the power of Interceptors? If not, now is the perfect time to refactor and enjoy the maintainability it brings!

If you have any questions or want to share your experience, feel free to leave a comment!


References:

Related Articles

💬 Comments