grpc/grpc-java · error · RuntimeException

There are fields in a LoadBalancingConfig object. Exactly…

Error message

There are ${size} fields in a LoadBalancingConfig object. Exactly one is expected. Config=${lbConfig}

What it means

gRPC service configs express the load balancing policy selection as a JSON object with exactly one entry: the policy name as key and its config object as value (e.g. {"round_robin":{}}). ServiceConfigUtil.unwrapLoadBalancingConfig enforces this one-entry shape; any other number of fields makes the selected policy ambiguous, so it throws a RuntimeException.

Solutions

  1. Ensure the object has exactly one key: the policy name mapped to its config object
  2. Split multiple policies into a list of single-entry objects: [{"grpclb":{}},{"round_robin":{}}] (list order = preference order)
  3. If no config is needed, still provide an empty object for the policy, e.g. {"round_robin":{}}

Example fix

// before
"loadBalancingConfig": {"round_robin": {}, "grpclb": {}}
// after
"loadBalancingConfig": [{"grpclb": {}}, {"round_robin": {}}]
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate loadBalancingConfig shape
if (lbConfig != null) {
  if (lbConfig.size() != 1) {
    throw new IllegalArgumentException(
        "loadBalancingConfig object must have exactly one entry, got " + lbConfig.size());
  }
}

Type guard

boolean isSingleEntryLbConfig(java.util.Map<String, ?> cfg) {
  return cfg != null && cfg.size() == 1;
}

Try / catch

try {
  LbConfig cfg = ServiceConfigUtil.unwrapLoadBalancingConfig(obj);
} catch (RuntimeException e) {
  log.error("Malformed loadBalancingConfig (need exactly one policy per object): " + e.getMessage());
  throw new ConfigInvalidException(e);
}

Prevention

When it happens

Trigger: Passing a loadBalancingConfig object with zero entries or multiple entries (e.g. {"round_robin":{},"grpclb":{}}) into unwrapLoadBalancingConfig, typically via unwrapLoadBalancingConfigList while parsing the service config.

Common situations: Hand-merged service configs where two policies were combined into one object; empty config objects; misunderstanding that each list element must contain exactly one policy, with selection priority expressed by the list order.

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/ad4e38dc8d5dde2c. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/io/grpc/internal/ServiceConfigUtil.java:313

      String policy = JsonUtil.getString(serviceConfig, "loadBalancingPolicy");
      if (policy != null) {
        // Convert the policy to a config, so that the caller can handle them in the same way.
        policy = policy.toLowerCase(Locale.ROOT);
        Map<String, ?> fakeConfig = Collections.singletonMap(policy, Collections.emptyMap());
        lbConfigs.add(fakeConfig);
      }
    }
    return Collections.unmodifiableList(lbConfigs);
  }

  /**
   * Unwrap a LoadBalancingConfig JSON object into a {@link LbConfig}.  The input is a JSON object
   * (map) with exactly one entry, where the key is the policy name and the value is a config object
   * for that policy.
   */
  public static LbConfig unwrapLoadBalancingConfig(Map<String, ?> lbConfig) {
    if (lbConfig.size() != 1) {
      throw new RuntimeException(
          "There are " + lbConfig.size() + " fields in a LoadBalancingConfig object. Exactly one"
          + " is expected. Config=" + lbConfig);
    }
    String key = lbConfig.entrySet().iterator().next().getKey();
    return new LbConfig(key, JsonUtil.getObject(lbConfig, key));
  }

  /**
   * Given a JSON list of LoadBalancingConfigs, and convert it into a list of LbConfig.
   */
  public static List<LbConfig> unwrapLoadBalancingConfigList(List<Map<String, ?>> list) {
    if (list == null) {
      return null;
    }
    ArrayList<LbConfig> result = new ArrayList<>();
    for (Map<String, ?> rawChildPolicy : list) {
      result.add(unwrapLoadBalancingConfig(rawChildPolicy));
    }

View on GitHub (pinned to 64daddc1f3)