gRPC server reflection works by having your server expose its own schema, allowing clients to discover services and methods at runtime without prior knowledge of the .proto files.

Let’s see this in action. Imagine a simple gRPC service definition:

syntax = "proto3";

package mypackage;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Without reflection, a client needs this .proto file to generate code and know how to call SayHello. With reflection enabled on the server, a dynamic client (like a CLI tool or a dynamically typed language) can inspect the server, discover the Greeter service, the SayHello method, and the expected request/response message structures, and then construct and send the gRPC call.

The core problem server reflection solves is decoupling client development from server-side schema changes. When you update your .proto files, you don’t need to force all clients to regenerate their code immediately. Dynamic clients can adapt on the fly. This is particularly useful for internal tooling, API gateways, or scenarios where you have a diverse set of clients that are difficult to coordinate for code regeneration.

Internally, server reflection leverages the grpc.reflection.v1alpha.ServerReflection service, which is part of the gRPC ecosystem. Your server implements this service, and it responds to requests from clients asking for information about:

  • Service listing: What services are available on the server?
  • Service details: For a given service, what methods does it have? What are the request and response types for each method?
  • Type information: For a given message type, what are its fields and their types?

To enable this, you typically need to:

  1. Import the reflection proto definitions: These are usually available in your gRPC installation or as separate packages. For Go, this might involve google.golang.org/grpc/reflection/grpc_reflection_v1alpha.
  2. Register the reflection service with your gRPC server: This involves creating an instance of the reflection service and registering it with your main grpc.Server.

Here’s a conceptual Go example of how you’d register the reflection service:

import (
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	// Your service implementation and generated code
)

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()

	// Register your actual services here
	// pb.RegisterGreeterServer(s, &server{})

	// Register the reflection service
	reflection.Register(s)

	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

This reflection.Register(s) call is the key. It tells the grpc.Server (s) to also handle requests directed to the ServerReflection service. When a client connects and asks for service information, this registered handler will process the request and return the metadata based on the services and types known to the server at that moment.

The surprising part is that enabling reflection doesn’t require modifying your existing service implementations or their .proto definitions. The reflection service is a separate, standard gRPC service that introspects the server’s registered methods and message descriptors. It’s a meta-layer that hooks into the gRPC framework’s understanding of your services.

The next logical step after enabling reflection is understanding how to build dynamic clients that can leverage it, such as using tools like grpcurl or writing custom client logic in dynamically typed languages.

Want structured learning?

Take the full Grpc course →