grpc/grpc-java · error · ResourceInvalidException
Maximum LB config recursion depth reached
Error message
Maximum LB config recursion depth reached
What it means
ResourceInvalidException thrown by LoadBalancerConfigFactory.newConfig when LoadBalancingPolicyConverter.convertToServiceConfig exceeds its recursion limit (MaxRecursionReachedException). Nested LB policy configurations (policies containing policies) are too deeply nested, so the factory rejects the cluster config as invalid to prevent unbounded recursion.
Source
Thrown at xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java:111
static final String ERROR_UTILIZATION_PENALTY = "errorUtilizationPenalty";
static final String METRIC_NAMES_FOR_COMPUTING_UTILIZATION = "metricNamesForComputingUtilization";
/**
* Factory method for creating a new {link LoadBalancerConfigConverter} for a given xDS {@link
* Cluster}.
*
* @throws ResourceInvalidException If the {@link Cluster} has an invalid LB configuration.
*/
static ImmutableMap<String, ?> newConfig(Cluster cluster, boolean enableLeastRequest)
throws ResourceInvalidException {
// The new load_balancing_policy will always be used if it is set, but for backward
// compatibility we will fall back to using the old lb_policy field if the new field is not set.
if (cluster.hasLoadBalancingPolicy()) {
try {
return LoadBalancingPolicyConverter.convertToServiceConfig(cluster.getLoadBalancingPolicy(),
0);
} catch (MaxRecursionReachedException e) {
throw new ResourceInvalidException("Maximum LB config recursion depth reached", e);
}
} else {
return LegacyLoadBalancingPolicyConverter.convertToServiceConfig(cluster, enableLeastRequest);
}
}
/**
* Builds a service config JSON object for the ring_hash load balancer config based on the given
* config values.
*/
private static ImmutableMap<String, ?> buildRingHashConfig(Long minRingSize, Long maxRingSize) {
ImmutableMap.Builder<String, Object> configBuilder = ImmutableMap.builder();
if (minRingSize != null) {
configBuilder.put(MIN_RING_SIZE_FIELD_NAME, minRingSize.doubleValue());
}
if (maxRingSize != null) {
configBuilder.put(MAX_RING_SIZE_FIELD_NAME, maxRingSize.doubleValue());
}View on GitHub (pinned to 64daddc1f3)
Solutions
- Flatten the LB policy chain on the control plane so nesting depth stays within the converter's limit
- Check for and break any self-referential/cyclic policy definitions in the cluster config
- Upgrade grpc-xds in case a newer release raised the supported recursion depth
- Replace composed custom policies with a single supported policy
Example fix
// before: deeply nested policy chain
"lb_policy": {"name": "a", "config": {"policy": {"name": "b", "config": {"policy": {"name": "a", ...}}}}}
// after: single flat policy
"lb_policy": {"name": "round_robin", "typed_config": {...}} Defensive patterns
Strategy: try-catch
Validate before calling
// bound-check nesting depth of policy configs before sending them
int depth = 0;
for (Node n = root; n != null; n = n.inner()) {
if (++depth > 10) throw new IllegalArgumentException("LB policy nesting too deep");
} Try / catch
try {
/* start xDS client with cluster */;
} catch (ResourceInvalidException e) {
if (e.getMessage().contains("Maximum LB config recursion depth")) {
logger.severe("Cluster LB policy nesting too deep: " + e.getMessage());
// fall back to default policy
} else { throw e; }
} Prevention
- Keep LB policy composition shallow (1-2 levels)
- Detect cyclic policy references in config generation tooling
- Test generated Envoy configs against grpc-java limits, not just Envoy's
- Prefer a single supported policy over deep custom-policy chains
When it happens
Trigger: newConfig processes a cluster whose load_balancing_policy contains deeply (or cyclically) nested policy configs — each nested policy increments the depth counter passed to the converter until the max depth is hit.
Common situations: Control plane misconfiguration that nests policies excessively or creates a self-referential policy chain; pathological generated configs from a policy-composition tool; Envoy config meant for Envoy's deeper nesting tolerance but rejected by grpc-java's stricter limit.
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
- Invalid LoadBalancingPolicy: " + loadBalancingPolicy
- Invalid bootstrap: 'xds_servers' is empty
- Invalid bootstrap: server ${serverUri} 'channel_creds' requi
- Server ${serverUri}: no supported channel credentials found
- Invalid bootstrap: server ${serverUri} with 'channel_creds'
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/e8199dab33809e4f.
Report an issue: GitHub.