grpc/grpc-java · error · ResourceInvalidException
Multiple ClusterSpecifierPlugins with the same name
Error message
Multiple ClusterSpecifierPlugins with the same name: ${pluginName} What it means
A RouteConfiguration may declare multiple ClusterSpecifierPlugins, but each must have a unique extension name. While building the pluginConfigMap in extractVirtualHosts, a put() returning a previous value reveals a duplicate name, so the RouteConfiguration is rejected as invalid.
Solutions
- Rename one of the duplicate ClusterSpecifierPlugins so extension names are unique
- Remove the redundant duplicate plugin entry
- Fix the control plane/templating that merges plugin lists without deduplication
Example fix
// before
cluster_specifier_plugins: [
{extension: {name: "pluginA", ...}},
{extension: {name: "pluginA", ...}}
]
// after
cluster_specifier_plugins: [
{extension: {name: "pluginA", ...}},
{extension: {name: "pluginB", ...}}
] Defensive patterns
Strategy: validation
Validate before calling
// Detect duplicate plugin names before applying
Set<String> seen = new HashSet<>();
for (ClusterSpecifierPlugin p : rc.getClusterSpecifierPluginsList()) {
if (!seen.add(p.getExtension().getName())) {
throw new IllegalArgumentException("Duplicate plugin: " + p.getExtension().getName());
}
} Try / catch
try {
xdsClient.watchResource(ROUTE_CONFIGURATION, name, watcher);
} catch (ResourceInvalidException e) {
log.error("RouteConfiguration invalid: {}", e.getMessage());
} Prevention
- Deduplicate plugin lists when merging config fragments
- Enforce unique extension names in the control plane schema
- Run config linting in CI before publishing RouteConfigurations
When it happens
Trigger: processRouteConfiguration -> extractVirtualHosts iterating routeConfig.getClusterSpecifierPluginsList() where two plugins share the same extension().getName(); the earlier entry was accepted (non-null PluginConfig) and the duplicate overwrites it.
Common situations: Generated RouteConfiguration merging plugin lists from multiple sources; copy-pasted plugin blocks with the same name but different configs; templating tools concatenating plugin arrays.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- RouteConfiguration contains invalid virtual host
- 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/e39ac46f154824dd.
Report an issue: GitHub.
Appendix: source
Thrown at xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java:157
RouteConfiguration routeConfig, FilterRegistry filterRegistry, XdsResourceType.Args args)
throws ResourceInvalidException {
return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry, args));
}
static List<VirtualHost> extractVirtualHosts(
RouteConfiguration routeConfig, FilterRegistry filterRegistry, XdsResourceType.Args args)
throws ResourceInvalidException {
Map<String, PluginConfig> pluginConfigMap = new HashMap<>();
ImmutableSet.Builder<String> optionalPlugins = ImmutableSet.builder();
if (enableRouteLookup) {
List<ClusterSpecifierPlugin> plugins = routeConfig.getClusterSpecifierPluginsList();
for (ClusterSpecifierPlugin plugin : plugins) {
String pluginName = plugin.getExtension().getName();
PluginConfig pluginConfig = parseClusterSpecifierPlugin(plugin);
if (pluginConfig != null) {
if (pluginConfigMap.put(pluginName, pluginConfig) != null) {
throw new ResourceInvalidException(
"Multiple ClusterSpecifierPlugins with the same name: " + pluginName);
}
} else {
// The plugin parsed successfully, and it's not supported, but it's marked as optional.
optionalPlugins.add(pluginName);
}
}
}
List<VirtualHost> virtualHosts = new ArrayList<>(routeConfig.getVirtualHostsCount());
for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto
: routeConfig.getVirtualHostsList()) {
StructOrError<VirtualHost> virtualHost =
parseVirtualHost(virtualHostProto, filterRegistry, pluginConfigMap,
optionalPlugins.build(), args);
if (virtualHost.getErrorDetail() != null) {
throw new ResourceInvalidException(
"RouteConfiguration contains invalid virtual host: " + virtualHost.getErrorDetail());
}View on GitHub (pinned to 64daddc1f3)