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

  1. 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
  2. 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
  3. Raise MAX_RECURSION_DEPTH only if you control the build and understand stack-overflow risk — prefer reconfiguring instead
  4. 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

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


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