{"record":{"id":"b99465b55aecf020","repo":"grpc/grpc-java","slug":"onmatch-must-have-either-matcher-or-action","errorCode":null,"errorMessage":"OnMatch must have either matcher or action","messagePattern":"OnMatch must have either matcher or action","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/internal/matcher/OnMatch.java","lineNumber":45,"sourceCode":"final class OnMatch {\n  @Nullable private final UnifiedMatcher nestedMatcher;\n  @Nullable private final TypedExtensionConfig action;\n  final boolean keepMatching;\n  \n  OnMatch(Matcher.OnMatch proto, Predicate<String> actionValidator) {\n    this.keepMatching = proto.getKeepMatching();\n    if (proto.hasMatcher()) {\n      this.nestedMatcher = UnifiedMatcher.fromProto(proto.getMatcher(), actionValidator);\n      this.action = null;\n    } else if (proto.hasAction()) {\n      this.nestedMatcher = null;\n      this.action = proto.getAction();\n      String typeUrl = this.action.getTypedConfig().getTypeUrl();\n      if (!actionValidator.test(typeUrl)) {\n        throw new IllegalArgumentException(\"Unsupported action type: \" + typeUrl);\n      }\n    } else {\n      throw new IllegalArgumentException(\"OnMatch must have either matcher or action\");\n    }\n  }\n  \n  MatchResult evaluate(MatchContext context) {\n    if (nestedMatcher != null) {\n      return nestedMatcher.match(context);\n    }\n    return MatchResult.create(action);\n  }\n}\n","sourceCodeStart":27,"sourceCodeEnd":56,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/internal/matcher/OnMatch.java#L27-L56","documentation":"OnMatch is constructed from an xDS proto route's on_match field, which must define exactly one of: a nested matcher tree or a terminal action. If the proto sets neither, the constructor throws this IllegalArgumentException because the OnMatch object would have no behavior to apply when the parent matcher matches.","triggerScenarios":"Building an OnMatch from a Matcher.OnMatch proto where hasMatcher() is false and getAction() has no typed config set — e.g. a route or virtual host config where on_match is present but empty, or was stripped of both its matcher sub-message and its action during config generation.","commonSituations":"Malformed xDS route configurations from a management server (envoy-style route tables missing the recursive matcher or action); config conversion tools that emit an empty on_match placeholder; hand-written JSON/yaml xDS configs missing the matcher block.","solutions":["Fix the xDS config so on_match sets either a nested matcher (match_tree) or an action with a supported typed_config","Validate the route proto server-side before sending: check hasMatcher() || hasAction() for every on_match","Wrap xDS config parsing in try-catch for IllegalArgumentException and reject/skip the offending route with a clear log message"],"exampleFix":"// before: on_match with neither matcher nor action\non_match: {}\n// after\non_match:\n  action:\n    name: route_to_backend\n    typed_config:\n      '@type': type.googleapis.com/grpc.intent.RouteAction","handlingStrategy":"validation","validationCode":"// reject before construction\nif (!onMatchProto.hasMatcher() && onMatchProto.getAction().getTypedConfig().getTypeUrl().isEmpty()) {\n  throw new IllegalArgumentException(\"on_match must set matcher or action\");\n}","typeGuard":null,"tryCatchPattern":"try { OnMatch onMatch = new OnMatch(proto, actionValidator); } catch (IllegalArgumentException e) { log.warn(\"Skipping invalid on_match: \" + e.getMessage()); return null; }","preventionTips":["Always populate either matcher or action in on_match blocks","Validate route protos on the management server before delivery","Add config schema tests that cover empty on_match cases"],"tags":["xds","routing","config-validation","grpc"],"backgroundTag":"missing-required-argument","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}