grpc/grpc-java · error · ResourceInvalidException
FilterChainMatch must be unique. Found duplicate
Error message
FilterChainMatch must be unique. Found duplicate: ${cur} What it means
Filter chain matches in a Listener must be unambiguous: xDS flattens each FilterChainMatch into a cross-product of simple matches and requires each be unique across the listener. When the same flattened match is seen twice, XdsListenerResource throws ResourceInvalidException because traffic routing would be nondeterministic.
Solutions
- Find the two filter_chains with identical FilterChainMatch and make each match unique (narrow ports, destination_prefix_ranges, server_names, or application_protocols).
- Remove the redundant duplicate filter chain if it is not needed.
- Review wildcard/empty match fields: a chain with an empty match collides with any fully-specified chain sharing its source-type settings.
Example fix
// before: two chains both matching port 443 with no server_names
filter_chains: [{ filter_chain_match: { destination_port: 443 } }, { filter_chain_match: { destination_port: 443 } }]
// after: distinguish by server name or port
filter_chains: [{ filter_chain_match: { destination_port: 443, server_names: ["a.example.com"] } }, { filter_chain_match: { destination_port: 443, server_names: ["b.example.com"] } }] Defensive patterns
Strategy: validation
Validate before calling
// dedupe check on filter chain matches before submission
Set<String> seen = new HashSet<>();
for (FilterChain fc : listener.getFilterChainsList()) {
if (!seen.add(fc.getFilterChainMatch().toString())) {
throw new IllegalArgumentException("duplicate FilterChainMatch: " + fc.getFilterChainMatch());
}
} Try / catch
try { parse/apply listener } catch (ResourceInvalidException e) { if (e.getMessage().contains("FilterChainMatch must be unique")) fixDuplicateMatches(); } Prevention
- Lint Listener configs for overlapping match criteria in CI
- Remember empty match fields act as wildcards and can collide with specific chains
- Keep filter chain match fields mutually exclusive across chains
When it happens
Trigger: A Listener proto containing two filter_chains whose FilterChainMatch entries overlap exactly after flattening (same destination port, prefix ranges, application protocols, source ranges, server names, transport protocol), detected in validateFilterChainMatchForUniqueness during parseFilterChain.
Common situations: Copy-pasted filter chain blocks with identical match criteria but different tls_contexts, control-plane generation bugs emitting duplicate matches, or a partially edited config where wildcards (e.g. empty destination port, empty server name) accidentally broaden one chain to collide with another.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- HttpConnectionManager neither has inlined route_config nor…
- OnMatch must have either matcher or action
- 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/67030ede0656f50f.
Report an issue: GitHub.
Appendix: source
Thrown at xds/src/main/java/io/grpc/xds/XdsListenerResource.java:328
DownstreamTlsContext.OcspStaplePolicy ocspStaplePolicy = downstreamTlsContext
.getOcspStaplePolicy();
if (ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.UNRECOGNIZED
&& ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.LENIENT_STAPLING) {
throw new ResourceInvalidException(
"downstream-tls-context with ocsp_staple_policy value " + ocspStaplePolicy.name()
+ " is not supported");
}
return downstreamTlsContext;
}
private static void validateFilterChainMatchForUniqueness(
Set<FilterChainMatch> filterChainMatchSet,
FilterChainMatch filterChainMatch) throws ResourceInvalidException {
// Flattens complex FilterChainMatch into a list of simple FilterChainMatch'es.
List<FilterChainMatch> crossProduct = getCrossProduct(filterChainMatch);
for (FilterChainMatch cur : crossProduct) {
if (!filterChainMatchSet.add(cur)) {
throw new ResourceInvalidException("FilterChainMatch must be unique. "
+ "Found duplicate: " + cur);
}
}
}
private static List<FilterChainMatch> getCrossProduct(FilterChainMatch filterChainMatch) {
// repeating fields to process:
// prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames
List<FilterChainMatch> expandedList = expandOnPrefixRange(filterChainMatch);
expandedList = expandOnApplicationProtocols(expandedList);
expandedList = expandOnSourcePrefixRange(expandedList);
expandedList = expandOnSourcePorts(expandedList);
return expandOnServerNames(expandedList);
}
private static List<FilterChainMatch> expandOnPrefixRange(FilterChainMatch filterChainMatch) {
ArrayList<FilterChainMatch> expandedList = new ArrayList<>();
if (filterChainMatch.prefixRanges().isEmpty()) {View on GitHub (pinned to 64daddc1f3)