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
- Lowercase the header name before constructing: name.toLowerCase(Locale.ROOT).
- Normalize header names on the config producer/control plane before emitting xDS matchers.
- 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
- Normalize header names with toLowerCase(Locale.ROOT) before construction.
- Enforce lowercase header conventions on the control plane.
- Never copy CamelCase names from HTTP/1 docs or devtools into xDS matcher configs.
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
- Header name length must be in range [1, 16384): ${length}
- Malformed status code
- TLS ALPN negotiation failed with protocols: ${protocols}
- EOF trying to read ${length} bytes
- Invalid initial window size: ${newWindowSize}
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/4b09ee5d5d643908.
Report an issue: GitHub.