grpc/grpc-java · error · IllegalArgumentException

Invalid header name: ${headerName}

Error message

Invalid header name: ${headerName}

What it means

HeaderMatchInput validates an xDS header name by constructing a Metadata.Key; if the name is not a valid gRPC metadata key (must be lowercase ASCII letters, digits, hyphen, underscore, and dot), the underlying IllegalArgumentException is rethrown wrapped with the offending header name. This prevents invalid header names from silently failing to match at runtime.

Source

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

  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;
    }
    if (binaryKey != null) {
      Iterable<byte[]> values = context.getMetadata().getAll(binaryKey);
      if (values == null) {
        return null;
      }
      StringBuilder sb = new StringBuilder();
      boolean first = true;
      for (byte[] value : values) {
        if (!first) {
          sb.append(",");

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Check the header_name in the xDS HttpHeaderMatchInput config: it must be lowercase and only contain letters, digits, '-', '_', '.'.
  2. Lowercase and sanitize the header name (e.g. X-Request-Id -> x-request-id).
  3. If the name is dynamic, validate it before building the matcher with a regex like ^[a-z0-9_.-]+$.

Example fix

// before
{"header_name": "X-Request-Id"}
// after
{"header_name": "x-request-id"}
Defensive patterns

Strategy: validation

Validate before calling

static boolean isValidHeaderName(String name) {
  return name != null && name.matches("^[a-z0-9_.-]+$");
}
if (!isValidHeaderName(cfg.getHeaderName())) throw new IllegalArgumentException("bad header name: " + cfg.getHeaderName());

Type guard

static boolean isValidHeaderName(String name) {
  return name != null && name.matches("^[a-z0-9_.-]+$");
}

Try / catch

try {
  input = factory.getInput(config);
} catch (IllegalArgumentException e) {
  logger.warn("bad header match input: " + e.getMessage());
  throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription(e.getMessage()));
}

Prevention

When it happens

Trigger: Calling getInput with a TypedExtensionConfig whose HttpRequestHeaderMatchInput.header_name contains characters invalid for gRPC metadata keys (e.g. uppercase letters, spaces, colons) or is empty.

Common situations: Copy-pasting Envoy route config with HTTP-style header names like 'X-Request-Id' (uppercase not allowed for metadata keys) or hand-written CEL/rbac matcher configs with typos in the header name.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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