grpc/grpc-java · error · IllegalArgumentException
Unsupported "key" %s
Error message
Unsupported "key" %s
What it means
After confirming the header key is present, parseHeader rejects keys that start with ':' (pseudo-headers), start with 'grpc-' (reserved gRPC metadata), or appear in UNSUPPORTED_HEADERS (case-insensitive). These headers cannot be matched by authorization rules, so the translation fails fast.
Source
Thrown at authz/src/main/java/io/grpc/authz/AuthorizationPolicyTranslator.java:82
Principal.Set.Builder principalsSet = Principal.Set.newBuilder();
for (String principal: principalsList) {
principalsSet.addIds(
Principal.newBuilder().setAuthenticated(
Authenticated.newBuilder().setPrincipalName(
getStringMatcher(principal)).build()).build());
}
return Principal.newBuilder().setOrIds(principalsSet.build()).build();
}
private static Permission parseHeader(Map<String, ?> header) throws IllegalArgumentException {
String key = JsonUtil.getString(header, "key");
if (key == null || key.isEmpty()) {
throw new IllegalArgumentException("\"key\" is absent or empty");
}
if (key.charAt(0) == ':'
|| key.startsWith("grpc-")
|| UNSUPPORTED_HEADERS.contains(key.toLowerCase(Locale.ROOT))) {
throw new IllegalArgumentException(String.format("Unsupported \"key\" %s", key));
}
List<String> valuesList = JsonUtil.getListOfStrings(header, "values");
if (valuesList == null || valuesList.isEmpty()) {
throw new IllegalArgumentException("\"values\" is absent or empty");
}
Permission.Set.Builder orSet = Permission.Set.newBuilder();
for (String value: valuesList) {
orSet.addRules(
Permission.newBuilder().setHeader(
HeaderMatcher.newBuilder()
.setName(key)
.setStringMatch(getStringMatcher(value)).build()).build());
}
return Permission.newBuilder().setOrRules(orSet.build()).build();
}
private static Permission parseRequest(Map<String, ?> request) throws IllegalArgumentException {
Permission.Set.Builder andSet = Permission.Set.newBuilder();View on GitHub (pinned to 64daddc1f3)
Solutions
- Replace the unsupported key with a supported custom header, forwarding that data as application metadata
- Remove the offending rule if it targets reserved/internal headers
- For ":path"-style matching, use the policy's supported fields instead of header matchers
- Lower-case awareness: rename keys so none normalize into UNSUPPORTED_HEADERS
Example fix
// before
{"headers":[{"key":":path","values":["/api/*"]}]}
// after
{"headers":[{"key":"x-original-path","values":["/api/*"]}]} // forwarded as a regular header Defensive patterns
Strategy: validation
Validate before calling
static final Set<String> UNSUPPORTED = Set.of("content-length", "content-type", /* mirror UNSUPPORTED_HEADERS */);
static void checkHeaderKey(String key) {
if (key.charAt(0) == ':' || key.startsWith("grpc-") || UNSUPPORTED.contains(key.toLowerCase(Locale.ROOT)))
throw new IllegalArgumentException("Unsupported header key: " + key);
} Try / catch
try { AuthorizationPolicyTranslator.translate(policyJson, serverName); } catch (IllegalArgumentException e) { throw new PolicyValidationException("Policy uses an unsupported header: " + e.getMessage(), e); } Prevention
- Never use :pseudo-headers or grpc-* metadata in authz rules
- Check case-insensitively against reserved header lists
- Forward needed data via custom x-* headers
- Review Envoy-ported policies for pseudo-header usage
When it happens
Trigger: A policy JSON rule uses a header key like ":path", "grpc-timeout", or another unsupported/reserved header name in its headers list.
Common situations: Porting Envoy-style policies that use ":path" pseudo-headers; rules written against reserved grpc-* metadata; keys differing only in case caught by the lowercase check.
Related errors
- "key" is absent or empty
- "values" is absent or empty
- rule "name" is absent or empty
- Authorization policy should be a JSON object. Found: null
- "name" is absent or empty
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/21e89f24bfac9ff7.
Report an issue: GitHub.