grpc/grpc-go · error
invalid loadBalancingConfig: entry %v does not contain exact
Error message
invalid loadBalancingConfig: entry %v does not contain exactly 1 policy/config pair: %q
What it means
This error occurs in BalancerConfig.UnmarshalJSON when a loadBalancingConfig array entry is a JSON object that does not contain exactly one key (policy name -> config). gRPC's service config spec requires each entry to be a single-key map like [{"round_robin": {}}]. Entries with zero keys ({}) or multiple keys ({"a":{}, "b":{}}) are rejected.
Source
Thrown at internal/serviceconfig/serviceconfig.go:84
//
// ServiceConfig contains a list of loadBalancingConfigs, each with a name and
// config. This method iterates through that list in order, and stops at the
// first policy that is supported.
// - If the config for the first supported policy is invalid, the whole service
// config is invalid.
// - If the list doesn't contain any supported policy, the whole service config
// is invalid.
func (bc *BalancerConfig) UnmarshalJSON(b []byte) error {
var ir intermediateBalancerConfig
err := json.Unmarshal(b, &ir)
if err != nil {
return err
}
var names []string
for i, lbcfg := range ir {
if len(lbcfg) != 1 {
return fmt.Errorf("invalid loadBalancingConfig: entry %v does not contain exactly 1 policy/config pair: %q", i, lbcfg)
}
var (
name string
jsonCfg json.RawMessage
)
// Get the key:value pair from the map. We have already made sure that
// the map contains a single entry.
for name, jsonCfg = range lbcfg {
}
names = append(names, name)
builder := balancer.Get(name)
if builder == nil {
// If the balancer is not registered, move on to the next config.
// This is not an error.
continue
}View on GitHub (pinned to 03255a9237)
Solutions
- Ensure each loadBalancingConfig array element is a single-key JSON object: [{"policy_name": {config}}].
- Split multi-policy entries into separate array elements: [{"a": {}}, {"b": {}}] not [{"a": {}, "b": {}}].
- Validate the service config JSON structure against the gRPC service_config.proto before passing it to the client.
Example fix
// before (broken): two policies in one entry
"loadBalancingConfig": [{"round_robin": {}, "pick_first": {}}]
// after (valid): one policy per entry, ordered by preference
"loadBalancingConfig": [{"round_robin": {}}, {"pick_first": {}}] Defensive patterns
Strategy: validation
Validate before calling
// Validate that each loadBalancingConfig entry is a single-key map
func validateLoadBalancingConfig(raw []byte) error {
var entries []map[string]json.RawMessage
if err := json.Unmarshal(raw, &entries); err != nil { return err }
for i, e := range entries {
if len(e) != 1 {
return fmt.Errorf("entry %d must have exactly 1 policy, got %d", i, len(e))
}
}
return nil
} Try / catch
var bc serviceconfig.BalancerConfig
if err := json.Unmarshal(raw, &bc); err != nil {
if strings.Contains(err.Error(), "does not contain exactly 1") {
// fix the config source or use default balancer
}
return err
} Prevention
- Generate service config using gRPC's own BalancerConfig.MarshalJSON to ensure correct structure.
- Validate the JSON schema of service configs from control planes before applying.
- Document the single-key-per-entry rule for teams writing service configs.
When it happens
Trigger: Parsing a service config whose loadBalancingConfig array contains an entry with 0 or 2+ keys. For example: "loadBalancingConfig": [{"round_robin": {}, "grpclb": {}}] or "loadBalancingConfig": [{}].
Common situations: Malformed service config from a custom name resolver or xDS control plane, hand-crafted JSON that groups multiple policies into one object instead of separate array entries, or tooling that serializes balancer config incorrectly.
Related errors
- error parsing loadBalancingConfig for policy %q: %v
- invalid loadBalancingConfig: no supported policies found in
- malformed duration %q: missing seconds unit
- malformed duration %q: contains no numbers
- no SubConn is available
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/52acfcbc4d4f0a3d.
Report an issue: GitHub.