grpc/grpc-java · error · IllegalArgumentException
OnMatch must have either matcher or action
Error message
OnMatch must have either matcher or action
What it means
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.
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
Example fix
// before: on_match with neither matcher nor action
on_match: {}
// after
on_match:
action:
name: route_to_backend
typed_config:
'@type': type.googleapis.com/grpc.intent.RouteAction Defensive patterns
Strategy: validation
Validate before calling
// reject before construction
if (!onMatchProto.hasMatcher() && onMatchProto.getAction().getTypedConfig().getTypeUrl().isEmpty()) {
throw new IllegalArgumentException("on_match must set matcher or action");
} Try / catch
try { OnMatch onMatch = new OnMatch(proto, actionValidator); } catch (IllegalArgumentException e) { log.warn("Skipping invalid on_match: " + e.getMessage()); return null; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- FilterChainMatch must be unique. Found duplicate
- HttpConnectionManager neither has inlined route_config nor…
- A terminal HttpFilter must be the last filter
- AndMatcher must have at least 2 predicates
- client_listener_resource_name_template
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/b99465b55aecf020.
Report an issue: GitHub.
Appendix: source
Thrown at xds/src/main/java/io/grpc/xds/internal/matcher/OnMatch.java:45
final class OnMatch {
@Nullable private final UnifiedMatcher nestedMatcher;
@Nullable private final TypedExtensionConfig action;
final boolean keepMatching;
OnMatch(Matcher.OnMatch proto, Predicate<String> actionValidator) {
this.keepMatching = proto.getKeepMatching();
if (proto.hasMatcher()) {
this.nestedMatcher = UnifiedMatcher.fromProto(proto.getMatcher(), actionValidator);
this.action = null;
} else if (proto.hasAction()) {
this.nestedMatcher = null;
this.action = proto.getAction();
String typeUrl = this.action.getTypedConfig().getTypeUrl();
if (!actionValidator.test(typeUrl)) {
throw new IllegalArgumentException("Unsupported action type: " + typeUrl);
}
} else {
throw new IllegalArgumentException("OnMatch must have either matcher or action");
}
}
MatchResult evaluate(MatchContext context) {
if (nestedMatcher != null) {
return nestedMatcher.match(context);
}
return MatchResult.create(action);
}
}
View on GitHub (pinned to 64daddc1f3)