grpc/grpc-java · error · IllegalArgumentException
Unsupported custom_match matcher
Error message
Unsupported custom_match matcher: ${typeUrl} What it means
When a SinglePredicate uses custom_match, the typed config's type URL must be registered in the MatcherRegistry as a MatcherProvider. If no provider is registered for that type URL, the evaluator throws this IllegalArgumentException — the custom matcher extension is unknown to this runtime.
Solutions
- Register a MatcherProvider for the typeUrl via MatcherRegistry.getDefaultRegistry().register(...) before parsing
- Remove or replace the custom_match with a supported value_match
- Check gRPC/xDS version compatibility with the control plane and upgrade the client if it should support the extension
- Verify the '@type' URL string exactly matches the registered type (correct package/name, no typos)
Example fix
// before: parsing with unregistered provider
MatcherRegistry.getDefaultRegistry().register("type.googleapis.com/my.CustomMatcher",
config -> new MyCustomMatcher(config));
PredicateEvaluator.fromProto(predicate); // now works
// after
MatcherProvider provider = MatcherRegistry.getDefaultRegistry()
.getMatcherProvider(typeUrl);
if (provider == null) { throw new IllegalArgumentException(
"Unsupported custom_match matcher: " + typeUrl); } Defensive patterns
Strategy: try-catch
Validate before calling
String typeUrl = customMatch.getTypedConfig().getTypeUrl(); boolean supported = MatcherRegistry.getDefaultRegistry().getMatcherProvider(typeUrl) != null;
Try / catch
try { new PredicateEvaluator.SinglePredicateEvaluator(proto); } catch (IllegalArgumentException e) { fallbackToDefaultMatcher(proto); } Prevention
- Register all custom MatcherProviders at application startup
- Keep client gRPC/xDS versions in sync with the control plane
- Copy '@type' URLs exactly as documented, no ad-hoc edits
When it happens
Trigger: Calling SinglePredicateEvaluator with a Predicate whose custom_match TypedExtensionConfig has a typeUrl not present in MatcherRegistry.getDefaultRegistry() — e.g. '@type' points to a matcher extension type this gRPC version does not implement.
Common situations: Envoy-originated configs referencing envoy custom matcher extensions not ported to gRPC xDS; version skew where the control plane is newer than the client; typo in the type.googleapis.com URL; custom matcher plugin not registered via MatcherRegistry before parsing.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Unsupported input type
- A terminal HttpFilter must be the last filter
- Address is not an IP
- All xds transports for authority are in backoff
- AndMatcher must have at least 2 predicates
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/29112344ad4b9f50.
Report an issue: GitHub.
Appendix: source
Thrown at xds/src/main/java/io/grpc/xds/internal/matcher/PredicateEvaluator.java:65
private final MatchInput input;
private final Matcher matcher;
SinglePredicateEvaluator(Predicate.SinglePredicate proto) {
if (!proto.hasInput()) {
throw new IllegalArgumentException("SinglePredicate must have input");
}
this.input = UnifiedMatcher.resolveInput(proto.getInput());
if (proto.hasValueMatch()) {
Matchers.StringMatcher stringMatcher =
MatcherParser.parseStringMatcher(proto.getValueMatch());
this.matcher = new StringMatcherAdapter(stringMatcher);
} else if (proto.hasCustomMatch()) {
TypedExtensionConfig customConfig = proto.getCustomMatch();
MatcherProvider provider = MatcherRegistry.getDefaultRegistry()
.getMatcherProvider(customConfig.getTypedConfig().getTypeUrl());
if (provider == null) {
throw new IllegalArgumentException("Unsupported custom_match matcher: "
+ customConfig.getTypedConfig().getTypeUrl());
}
this.matcher = provider.getMatcher(customConfig);
} else {
throw new IllegalArgumentException(
"SinglePredicate must have either value_match or custom_match");
}
if (!input.outputType().equals(matcher.inputType())) {
throw new IllegalArgumentException("Type mismatch: input " + input.outputType().getName()
+ " not compatible with matcher " + matcher.inputType().getName());
}
}
@Override
boolean evaluate(MatchContext context) {
return matcher.match(input.apply(context));
}View on GitHub (pinned to 64daddc1f3)