gRPC Metadata and Headers
gRPC metadata is a set of key-value pairs that travel alongside an RPC call — separately from the main message payload. Metadata carries information about the call itself: who is calling, in what language, with what token, and for which trace ID. It is the gRPC equivalent of HTTP headers.
Metadata vs Message Payload
gRPC call structure: ┌───────────────────────────────────────────────────────┐ │ HTTP/2 HEADERS frame ← metadata lives here │ │ ───────────────────── │ │ :method = POST │ │ :path = /user.UserService/GetUser │ │ authorization = Bearer eyJhbGciOi... │ │ x-request-id = abc-123-def-456 │ │ accept-language = en-US │ ├───────────────────────────────────────────────────────┤ │ HTTP/2 DATA frame ← your message payload here │ │ ───────────────────── │ │ [protobuf-encoded GetUserRequest bytes] │ └───────────────────────────────────────────────────────┘
Two Types of Metadata
gRPC distinguishes between metadata sent at the start of a call and metadata sent at the end.
┌──────────────────────┬───────────────────────────────────────────┐ │ Type │ When It Travels │ ├──────────────────────┼───────────────────────────────────────────┤ │ Initial Metadata │ Before the first message, set by client │ │ │ Also sent by server at start of response │ ├──────────────────────┼───────────────────────────────────────────┤ │ Trailing Metadata │ After the last message, sent by server │ │ │ Carries the gRPC status code and message │ └──────────────────────┴───────────────────────────────────────────┘ Flow diagram: Client ──[initial metadata]──────────────────────────────► Server Client ──[request payload]───────────────────────────────► Server Client ◄──[initial metadata]──────────────────────────── Server Client ◄──[response payload]─────────────────────────── Server Client ◄──[trailing metadata + status code]────────────── Server
Metadata Key Naming Rules
Metadata keys follow specific rules that differ from HTTP header naming.
Rules: • Keys are case-insensitive: "Authorization" = "authorization" • Keys must use lowercase letters, digits, hyphens, and underscores • Binary metadata keys MUST end with "-bin" • Keys starting with "grpc-" are reserved for gRPC itself Examples: Allowed: authorization, x-request-id, user-locale, trace-id Binary: custom-data-bin (value will be base64-encoded) Reserved: grpc-status, grpc-message, grpc-timeout (do not override)
Sending Metadata from the Client
Go client — attaching metadata to a call:
import "google.golang.org/grpc/metadata"
// Create metadata
md := metadata.New(map[string]string{
"authorization": "Bearer eyJhbGciOiJIUzI1...",
"x-request-id": "abc-123",
"x-user-locale": "en-US",
})
// Attach to context — all calls using this context carry the metadata
ctx := metadata.NewOutgoingContext(context.Background(), md)
// Make the call with metadata attached
resp, err := userClient.GetUser(ctx, &pb.GetUserRequest{UserId: 42})
Python client:
metadata = [
("authorization", "Bearer eyJhbGciOiJIUzI1..."),
("x-request-id", "abc-123"),
]
response = stub.GetUser(request, metadata=metadata)
Java client (blocking stub):
Metadata headers = new Metadata();
Metadata.Key<String> authKey =
Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
headers.put(authKey, "Bearer eyJhbGciOiJIUzI1...");
UserServiceGrpc.UserServiceBlockingStub stubWithHeaders =
MetadataUtils.attachHeaders(stub, headers);
User user = stubWithHeaders.getUser(request);
Reading Metadata on the Server
Go server — reading incoming metadata:
import "google.golang.org/grpc/metadata"
func (s *server) GetUser(ctx context.Context,
req *pb.GetUserRequest) (*pb.User, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.InvalidArgument, "missing metadata")
}
// Read a specific key (returns a slice — keys can have multiple values)
authValues := md.Get("authorization")
if len(authValues) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
token := authValues[0]
// validate token...
return &pb.User{Id: req.UserId}, nil
}
Sending Metadata from the Server
The server can send both initial metadata (before the response message) and trailing metadata (after the response message).
Go server — sending metadata back to client:
import "google.golang.org/grpc"
import "google.golang.org/grpc/metadata"
func (s *server) GetUser(ctx context.Context,
req *pb.GetUserRequest) (*pb.User, error) {
// Send initial metadata (before the response)
initialMD := metadata.Pairs(
"server-version", "2.1.0",
"cache-hint", "ttl=300",
)
grpc.SendHeader(ctx, initialMD)
// Do the work
user := lookupUser(req.UserId)
// Send trailing metadata (after the response)
trailingMD := metadata.Pairs(
"processing-time-ms", "14",
"db-query-count", "2",
)
grpc.SetTrailer(ctx, trailingMD)
return user, nil
}
Reading Server Metadata on the Client
Go client — reading metadata returned by server:
var header, trailer metadata.MD
resp, err := userClient.GetUser(
ctx,
&pb.GetUserRequest{UserId: 42},
grpc.Header(&header), // capture initial metadata
grpc.Trailer(&trailer), // capture trailing metadata
)
serverVersion := header.Get("server-version")
processingTime := trailer.Get("processing-time-ms")
Common Metadata Use Cases
┌────────────────────────┬───────────────────────────────────────────┐ │ Use Case │ Metadata Key Example │ ├────────────────────────┼───────────────────────────────────────────┤ │ Authentication │ authorization: Bearer <token> │ │ Distributed tracing │ x-trace-id: abc123, x-span-id: def456 │ │ Rate limiting │ x-api-key: key_abc123 │ │ Internationalisation │ accept-language: fr-FR │ │ Request routing │ x-tenant-id: customer_99 │ │ Debug information │ x-debug-mode: true │ │ Server response info │ x-rate-limit-remaining: 490 │ └────────────────────────┴───────────────────────────────────────────┘
Binary Metadata
Some metadata values are not valid UTF-8 text — for example, raw certificate bytes or serialised binary tokens. Keys ending in -bin signal binary values. gRPC automatically base64-encodes them for transport.
Sending binary metadata in Go:
binaryValue := []byte{0x01, 0x02, 0x03, 0xFF}
md := metadata.Pairs("custom-token-bin",
string(binaryValue)) // gRPC base64-encodes this automatically
ctx := metadata.NewOutgoingContext(ctx, md)
Summary
Metadata travels in HTTP/2 header frames alongside — but separate from — the message payload. Initial metadata travels before the first message. Trailing metadata travels after the last message and always carries the gRPC status code. Common uses include authentication tokens, tracing IDs, and locale settings. Binary metadata uses keys ending in -bin and is base64-encoded automatically.
