{"record":{"id":"e39ac46f154824dd","repo":"grpc/grpc-java","slug":"multiple-clusterspecifierplugins-with-the-same-nam","errorCode":null,"errorMessage":"Multiple ClusterSpecifierPlugins with the same name: ${pluginName}","messagePattern":"Multiple ClusterSpecifierPlugins with the same name: (.+?)","errorType":"exception","errorClass":"ResourceInvalidException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java","lineNumber":157,"sourceCode":"      RouteConfiguration routeConfig, FilterRegistry filterRegistry, XdsResourceType.Args args)\n      throws ResourceInvalidException {\n    return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry, args));\n  }\n\n  static List<VirtualHost> extractVirtualHosts(\n      RouteConfiguration routeConfig, FilterRegistry filterRegistry, XdsResourceType.Args args)\n      throws ResourceInvalidException {\n    Map<String, PluginConfig> pluginConfigMap = new HashMap<>();\n    ImmutableSet.Builder<String> optionalPlugins = ImmutableSet.builder();\n\n    if (enableRouteLookup) {\n      List<ClusterSpecifierPlugin> plugins = routeConfig.getClusterSpecifierPluginsList();\n      for (ClusterSpecifierPlugin plugin : plugins) {\n        String pluginName = plugin.getExtension().getName();\n        PluginConfig pluginConfig = parseClusterSpecifierPlugin(plugin);\n        if (pluginConfig != null) {\n          if (pluginConfigMap.put(pluginName, pluginConfig) != null) {\n            throw new ResourceInvalidException(\n                \"Multiple ClusterSpecifierPlugins with the same name: \" + pluginName);\n          }\n        } else {\n          // The plugin parsed successfully, and it's not supported, but it's marked as optional.\n          optionalPlugins.add(pluginName);\n        }\n      }\n    }\n    List<VirtualHost> virtualHosts = new ArrayList<>(routeConfig.getVirtualHostsCount());\n    for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto\n        : routeConfig.getVirtualHostsList()) {\n      StructOrError<VirtualHost> virtualHost =\n          parseVirtualHost(virtualHostProto, filterRegistry, pluginConfigMap,\n              optionalPlugins.build(), args);\n      if (virtualHost.getErrorDetail() != null) {\n        throw new ResourceInvalidException(\n            \"RouteConfiguration contains invalid virtual host: \" + virtualHost.getErrorDetail());\n      }","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java#L139-L175","documentation":"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.","triggerScenarios":"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.","commonSituations":"Generated RouteConfiguration merging plugin lists from multiple sources; copy-pasted plugin blocks with the same name but different configs; templating tools concatenating plugin arrays.","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"],"exampleFix":"// before\ncluster_specifier_plugins: [\n  {extension: {name: \"pluginA\", ...}},\n  {extension: {name: \"pluginA\", ...}}\n]\n// after\ncluster_specifier_plugins: [\n  {extension: {name: \"pluginA\", ...}},\n  {extension: {name: \"pluginB\", ...}}\n]","handlingStrategy":"validation","validationCode":"// Detect duplicate plugin names before applying\nSet<String> seen = new HashSet<>();\nfor (ClusterSpecifierPlugin p : rc.getClusterSpecifierPluginsList()) {\n  if (!seen.add(p.getExtension().getName())) {\n    throw new IllegalArgumentException(\"Duplicate plugin: \" + p.getExtension().getName());\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  xdsClient.watchResource(ROUTE_CONFIGURATION, name, watcher);\n} catch (ResourceInvalidException e) {\n  log.error(\"RouteConfiguration invalid: {}\", e.getMessage());\n}","preventionTips":["Deduplicate plugin lists when merging config fragments","Enforce unique extension names in the control plane schema","Run config linting in CI before publishing RouteConfigurations"],"tags":["xds","grpc","route-configuration","duplicate-key"],"backgroundTag":"conflicting-config-options","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"}