grpc/grpc-java · error · ResourceInvalidException

RouteConfiguration contains invalid virtual host

Error message

RouteConfiguration contains invalid virtual host: ${virtualHost.getErrorDetail()}

What it means

One of the RouteConfiguration's virtual hosts failed validation: parseVirtualHost returned a StructOrError carrying an errorDetail (e.g. invalid domains, bad route match, or unsupported route action). The whole RouteConfiguration is rejected because every virtual host must be valid.

Solutions

  1. Read the embedded errorDetail in the message — it names the exact vhost validation failure
  2. Fix the offending virtual host (domains, routes, or action) per the detail
  3. Remove or simplify routes using unsupported Envoy features
  4. Validate the RouteConfiguration against gRPC xDS-supported route semantics before deployment

Example fix

// before
virtual_hosts: [{name: "a", domains: [""], routes: [{}]}] // empty route action
// after
virtual_hosts: [{
  name: "a",
  domains: ["example.com"],
  routes: [{match: {prefix: "/"}, route: {cluster: "service"}}]
}]
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate each virtual host minimally
for (VirtualHostProto vh : rc.getVirtualHostsList()) {
  if (vh.getDomainsList().isEmpty() || vh.getRoutesList().isEmpty()) {
    throw new IllegalArgumentException("vhost " + vh.getName() + " missing domains/routes");
  }
}

Try / catch

try {
  xdsClient.watchResource(ROUTE_CONFIGURATION, name, watcher);
} catch (ResourceInvalidException e) {
  // message embeds the per-vhost errorDetail
  log.error("Invalid vhost: {}", e.getMessage());
}

Prevention

When it happens

Trigger: extractVirtualHosts parsing routeConfig.getVirtualHostsList(); parseVirtualHost returns non-null errorDetail — e.g. a vhost with empty/overlapping domains, routes with no matching action, or invalid cluster_specifier_plugin references.

Common situations: Route configs from Envoy using features gRPC xDS doesn't support (redirect, direct_response nuances, weighted clusters); duplicate/invalid domains in vhosts; routes referencing nonexistent plugin names.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/fe238916a27f33bf. Report an issue: GitHub.

Appendix: source

Thrown at xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java:173

        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());
      }
      virtualHosts.add(virtualHost.getStruct());
    }
    return virtualHosts;
  }

  private static StructOrError<VirtualHost> parseVirtualHost(
      io.envoyproxy.envoy.config.route.v3.VirtualHost proto, FilterRegistry filterRegistry,
       Map<String, PluginConfig> pluginConfigMap,
      Set<String> optionalPlugins, XdsResourceType.Args args) {
    String name = proto.getName();
    List<Route> routes = new ArrayList<>(proto.getRoutesCount());
    for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) {
      StructOrError<Route> route = parseRoute(
          routeProto, filterRegistry, pluginConfigMap, optionalPlugins, args);
      if (route == null) {
        continue;

View on GitHub (pinned to 64daddc1f3)