grpc/grpc-java · error · IllegalArgumentException
Matcher tree depth exceeds limit of 16
Error message
Matcher tree depth exceeds limit of 16
What it means
UnifiedMatcher.fromProto recursively converts an xDS Matcher proto into an in-process matcher tree. To prevent stack overflow / resource exhaustion from pathologically deep matcher configurations, checkRecursionDepth enforces MAX_RECURSION_DEPTH (16). If a matcher proto nests more than 16 levels of nested matchers (via matcher_list field matchers' on_match.matcher or on_no_match chains), IllegalArgumentException is thrown.
Source
Thrown at xds/src/main/java/io/grpc/xds/internal/matcher/UnifiedMatcher.java:69
Matcher.OnMatch onNoMatch = proto.hasOnNoMatch() ? proto.getOnNoMatch() : null;
if (proto.hasMatcherList()) {
return new MatcherList(proto.getMatcherList(), onNoMatch, actionValidator);
} else if (proto.hasMatcherTree()) {
return new MatcherTree(proto.getMatcherTree(), onNoMatch, actionValidator);
}
return new NoOpMatcher(onNoMatch, actionValidator);
}
/**
* Parses a proto Matcher into a UnifiedMatcher, allowing all actions.
*/
static UnifiedMatcher fromProto(Matcher proto) {
return fromProto(proto, (typeUrl) -> true);
}
private static void checkRecursionDepth(Matcher proto, int currentDepth) {
if (currentDepth > MAX_RECURSION_DEPTH) {
throw new IllegalArgumentException(
"Matcher tree depth exceeds limit of " + MAX_RECURSION_DEPTH);
}
if (proto.hasMatcherList()) {
for (Matcher.MatcherList.FieldMatcher fm : proto.getMatcherList().getMatchersList()) {
if (fm.hasOnMatch() && fm.getOnMatch().hasMatcher()) {
checkRecursionDepth(fm.getOnMatch().getMatcher(), currentDepth + 1);
}
}
} else if (proto.hasMatcherTree()) {
Matcher.MatcherTree tree = proto.getMatcherTree();
if (tree.hasExactMatchMap()) {
for (Matcher.OnMatch onMatch : tree.getExactMatchMap().getMapMap().values()) {
if (onMatch.hasMatcher()) {
checkRecursionDepth(onMatch.getMatcher(), currentDepth + 1);
}
}
} else if (tree.hasPrefixMatchMap()) {
for (Matcher.OnMatch onMatch : tree.getPrefixMatchMap().getMapMap().values()) {View on GitHub (pinned to 64daddc1f3)
Solutions
- Flatten or reduce the nesting depth of the Matcher proto so the on_match/on_no_match matcher chain is at most 16 levels deep
- Inspect the xDS config from the management server (grpc-xds debug logging) and refactor deeply nested matchers into sequential/multiple matchers or a custom plugin
- Raise MAX_RECURSION_DEPTH only if you control the build and understand stack-overflow risk — prefer reconfiguring instead
- Guard on the control-plane side to validate matcher depth before sending config to clients
Example fix
// before: deeply nested generated matcher (17+ levels of on_match.matcher) Matcher root = /* 17-level nested chain from control plane */; UnifiedMatcher m = UnifiedMatcher.fromProto(root); // throws // after: restructure on control plane to nest <= 16 levels, or merge conditions Matcher root = flattenMatcherChain(proto, 16); // collapse middle layers UnifiedMatcher m = UnifiedMatcher.fromProto(root); // ok
Defensive patterns
Strategy: validation
Validate before calling
// Count nested matcher depth before calling fromProto
int depth(Matcher m) {
int max = 0;
if (m.hasMatcherList()) {
for (Matcher.MatcherList.FieldMatcher fm : m.getMatcherList().getMatchersList()) {
if (fm.hasOnMatch() && fm.getOnMatch().hasMatcher()) max = Math.max(max, 1 + depth(fm.getOnMatch().getMatcher()));
}
}
return max;
}
if (depth(proto) > 16) throw new IllegalArgumentException("xDS matcher too deep (max 16)"); Prevention
- Keep xDS matcher nesting under 16 levels on the control plane
- Add config-time validation of matcher depth before sending to clients
- Enable xDS config logging to spot deep matcher chains early
When it happens
Trigger: Calling UnifiedMatcher.fromProto (directly or via xDS route/config parsing) with a Matcher proto whose nested on_match/on_no_match matcher chain is deeper than 16 levels; checkRecursionDepth increments depth per nested matcher and throws when currentDepth > MAX_RECURSION_DEPTH.
Common situations: Control planes (or hand-crafted envoy config) emitting deeply chained matchers; config generated by recursive tooling or nested per-route match policies; accidental infinite/near-infinite recursion in generated matcher protos from a misbehaving management server.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Fail to read bootstrap file
- No xds servers found for authority
- Provider not found for ${pluginName}
- Not implemented
- Unknown negotiation type: ${negotiationType}
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/d79d45bcae85848c.
Report an issue: GitHub.