grpc/grpc-java · error · IllegalArgumentException

Header name must be lowercase: ${headerName}

Error message

Header name must be lowercase: ${headerName}

What it means

HTTP/2 requires header names to be lowercase; this xDS matcher input will look headers up via gRPC Metadata keys, which are case-insensitive-lowercase by convention. HeaderMatchInput therefore rejects mixed/uppercase names with IllegalArgumentException to prevent silently unmatched lookups at request time.

Source

Thrown at xds/src/main/java/io/grpc/xds/internal/matcher/HeaderMatchInput.java:47

 * MatchInput for extracting HTTP headers.
 */
final class HeaderMatchInput implements MatchInput {
  private static final BaseEncoding BASE64 = BaseEncoding.base64();
  private final String headerName;
  private final Metadata.Key<byte[]> binaryKey;
  private final Metadata.Key<String> stringKey;

  static final String TYPE_URL =
      "type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput";
    
  HeaderMatchInput(String headerName) {
    this.headerName = checkNotNull(headerName, "headerName");
    if (headerName.isEmpty() || headerName.length() >= 16384) {
      throw new IllegalArgumentException(
          "Header name length must be in range [1, 16384): " + headerName.length());
    }
    if (!headerName.equals(headerName.toLowerCase(Locale.ROOT))) {
      throw new IllegalArgumentException("Header name must be lowercase: " + headerName);
    }
    try {
      if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
        this.binaryKey = Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER);
        this.stringKey = null;
      } else {
        this.binaryKey = null;
        this.stringKey = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
      }
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Invalid header name: " + headerName, e);
    }
  }
    
  @Override
  public String apply(MatchContext context) {
    if ("te".equals(headerName)) {
      return null;

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Lowercase the header name before constructing: name.toLowerCase(Locale.ROOT).
  2. Normalize header names on the config producer/control plane before emitting xDS matchers.
  3. Catch IllegalArgumentException and surface a message pointing at the exact offending header name.

Example fix

// before
new HeaderMatchInput("X-Request-Id");
// after
new HeaderMatchInput("x-request-id");
Defensive patterns

Strategy: validation

Validate before calling

String normalized = name.toLowerCase(Locale.ROOT);
if (!normalized.equals(name)) throw new IllegalArgumentException("header name must be lowercase: " + name);

Type guard

boolean isLowercaseHeaderName(String name) {
  return name != null && name.equals(name.toLowerCase(Locale.ROOT));
}

Try / catch

try { return new HeaderMatchInput(name); }
catch (IllegalArgumentException e) { logger.warn("non-lowercase header: " + name); return null; }

Prevention

When it happens

Trigger: Constructing HeaderMatchInput with a name containing uppercase characters, e.g. "X-User-Id", failing the check headerName.equals(headerName.toLowerCase(Locale.ROOT)).

Common situations: Configs authored against HTTP/1-style CamelCase header conventions; copy-pasting header names from documentation or browser devtools where names display capitalized; control planes forwarding user-written header names unnormalized.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/4b09ee5d5d643908. Report an issue: GitHub.