grpc/grpc-go · error
there is an empty key in the header
Error message
there is an empty key in the header
What it means
Returned by metadata.ValidateKey when the supplied header key is the empty string. gRPC metadata keys must be non-empty and lowercase; an empty key is treated as malformed per the internal address metadata validator. Pseudo-headers (leading ':') and binary headers (-bin) are exempt.
Source
Thrown at internal/metadata/metadata.go:107
// hasNotPrintable return true if msg contains any characters which are not in %x20-%x7E
func hasNotPrintable(msg string) bool {
// for i that saving a conversion if not using for range
for i := 0; i < len(msg); i++ {
if msg[i] < 0x20 || msg[i] > 0x7E {
return true
}
}
return false
}
// ValidateKey validates a key with the following rules (pseudo-headers are
// skipped):
// - the key must contain one or more characters.
// - the characters in the key must be in [0-9 a-z _ - .].
func ValidateKey(key string) error {
// key should not be empty
if key == "" {
return fmt.Errorf("there is an empty key in the header")
}
// pseudo-header will be ignored
if key[0] == ':' {
return nil
}
// check key, for i that saving a conversion if not using for range
for i := 0; i < len(key); i++ {
r := key[i]
if !(r >= 'a' && r <= 'z') && !(r >= '0' && r <= '9') && r != '.' && r != '-' && r != '_' {
return fmt.Errorf("header key %q contains illegal characters not in [0-9a-z-_.]", key)
}
}
return nil
}
// ValidatePair validates a key-value pair with the following rules
// (pseudo-header are skipped):
// - the key must contain one or more characters.View on GitHub (pinned to 03255a9237)
Solutions
- Filter out empty keys before constructing the metadata.MD.
- Validate keys with metadata.ValidateKey (or this internal.ValidatePair) before passing to Set/Validate.
- Log the offending key at the boundary where user input enters your code.
Example fix
// before
md := metadata.MD{"": []string{"v"}, "auth": []string{"t"}}
metadata.Set(addr, md) // empty key error
// after
md := metadata.MD{"auth": []string{"t"}}
for k, v := range userInput {
if k == "" { continue }
md[k] = v
} Defensive patterns
Strategy: validation
Validate before calling
// Strip empty keys before building metadata.
func sanitizeMD(md metadata.MD) metadata.MD {
out := metadata.MD{}
for k, v := range md {
if k == "" { continue }
out[k] = v
}
return out
} Type guard
func hasNoEmptyKeys(md metadata.MD) bool {
for k := range md { if k == "" { return false } }
return true
} Try / catch
if err := metadata.Validate(md); err != nil {
md = sanitizeMD(md) // drop bad entries and retry
} Prevention
- Validate metadata once at construction with metadata.Validate.
- Never derive keys from untrusted input without filtering empties.
- Unit-test metadata builders with blank-key inputs.
When it happens
Trigger: Calling metadata.Set(addr, md) where md contains a "" key, or metadata.Validate / ValidatePair with an empty key. Constructing a metadata.MD from user input without filtering blanks.
Common situations: Building metadata from a map with a missing/blank key; splitting a raw header line on ':' and using an empty left side; deserializing config where a header entry has no name.
Related errors
- header key %q contains illegal characters not in [0-9a-z-_.]
- header key %q contains value with non-printable ASCII charac
- no error details for status with code OK
- duplicated name
- "headers" %d: unsupported "key" %s
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/2e48e30191765bb1.
Report an issue: GitHub.