grpc/grpc-java · error · IllegalArgumentException
keys in KeyBuilder must be unique
Error message
keys in KeyBuilder must be unique
What it means
The RLS KeyBuilder collects request-header keys: constant keys, extra keys, and per-NameMatcher keys. Because all of these become distinct keys in the RLS request, duplicates are forbidden; checkUniqueKey throws this IllegalArgumentException when the combined set is smaller than the sum of the individual counts, proving at least one duplicate key.
Solutions
- Review all keys passed to constantKeys, EXTRA_KEY_NAMES, and every NameMatcher; rename or remove duplicates so each key appears exactly once.
- If a key must have both a constant and matcher behavior, keep only one and restructure the matching config.
- Pre-validate uniqueness in code: collect keys into a Set and assert size before building the RLS policy.
- Document reserved extra keys for the team so no custom key collides with them.
Example fix
// before
KeyBuilder.create().addConstantKey("env", "prod").addNameMatcher(NameMatcher.create("env"));
// after
KeyBuilder.create().addConstantKey("env", "prod"); // matcher removed; key used once Defensive patterns
Strategy: validation
Validate before calling
static void assertKeysUnique(Set<String> constants, Set<String> extras, List<NameMatcher> matchers) {
Set<String> all = new HashSet<>();
all.addAll(constants);
all.addAll(extras);
matchers.forEach(m -> all.add(m.key()));
if (all.size() != constants.size() + extras.size() + matchers.size()) {
throw new IllegalArgumentException("duplicate RLS keys");
}
} Prevention
- Keep a single source of truth for RLS keys and derive all three key groups from it.
- Document reserved extra-key names so custom keys cannot collide.
- Unit-test KeyBuilder construction with the production config.
- Fail at config-load time, not at request time, by building the policy eagerly.
When it happens
Trigger: Building an RLS KeyBuilder where the same key string appears in more than one place: a constant key equal to a NameMatcher key, two NameMatchers sharing a key, or a key colliding with the reserved extra keys ('host', 'path', 'service', 'method' style EXTRA_KEY_NAMES).
Common situations: Configuring RLS header matching where a developer adds both a constant value and a header matcher for the same header, or unintentionally reuses a key that the extra-keys set reserves.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- The lookupService field is not valid URI
- Authorization policy should be a JSON object. Found: null
- Number expected to be integer:
- Number expected to be long:
- The entry ' ' is of type ' ', which is not supported
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/b20a451483abc2ff.
Report an issue: GitHub.
Appendix: source
Thrown at rls/src/main/java/io/grpc/rls/RlsProtoConverters.java:274
(Map<String, String>) JsonUtil.getObject(keyBuilder, "constantKeys");
if (constantKeys == null) {
constantKeys = ImmutableMap.of();
}
ImmutableList<NameMatcher> nameMatchers = nameMatchersBuilder.build();
checkUniqueKey(nameMatchers, constantKeys.keySet());
return GrpcKeyBuilder.create(
namesBuilder.build(), nameMatchers, extraKeys, ImmutableMap.copyOf(constantKeys));
}
}
private static void checkUniqueKey(List<NameMatcher> nameMatchers, Set<String> constantKeys) {
Set<String> keys = new HashSet<>(constantKeys);
keys.addAll(EXTRA_KEY_NAMES);
for (NameMatcher nameMatcher : nameMatchers) {
keys.add(nameMatcher.key());
}
if (keys.size() != nameMatchers.size() + constantKeys.size() + EXTRA_KEY_NAMES.size()) {
throw new IllegalArgumentException("keys in KeyBuilder must be unique");
}
}
private RlsProtoConverters() {}
}
View on GitHub (pinned to 64daddc1f3)