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

  1. Filter out empty keys before constructing the metadata.MD.
  2. Validate keys with metadata.ValidateKey (or this internal.ValidatePair) before passing to Set/Validate.
  3. 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

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


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/2e48e30191765bb1. Report an issue: GitHub.