grpc/grpc-go · error

invalid loadBalancingConfig: entry

Error message

invalid loadBalancingConfig: entry %v does not contain exactly 1 policy/config pair: %q

What it means

Fires in BalancerConfig.UnmarshalJSON (serviceconfig.go:84) when an element of the loadBalancingConfig JSON array is not a single-key map mapping one policy name to its config object. gRPC's service-config schema requires each loadBalancingConfig entry to contain exactly one policy-name -> config pair, so the parser can walk them in order and pick the first supported one.

Solutions

  1. Reformat the offending entry so each array element is an object with exactly one key: [{"policyA": {}}, {"policyB": {}}].
  2. Use the library's BalancerConfig.MarshalJSON to generate valid output from a Go struct instead of hand-writing JSON.
  3. Validate the service config against the official gRPC service_config.proto JSON schema before applying.

Example fix

// before
// "loadBalancingConfig": [
//   { "round_robin": {}, "pick_first": {} }   // two keys in one entry
// ]

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

Strategy: validation

Validate before calling

// Validate that each loadBalancingConfig entry is a single-key map.
func validateLBConfig(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 has %d keys; want exactly 1", i, len(e))
        }
    }
    return nil
}

Try / catch

var bc svcconfig.BalancerConfig
if err := json.Unmarshal(lbJSON, &bc); err != nil {
    // err contains "does not contain exactly 1 policy/config pair"
    return err
}

Prevention

When it happens

Trigger: A loadBalancingConfig array entry whose JSON object has zero keys (e.g. [{}]) or two-or-more keys (e.g. [{"round_robin": {}, "grpclb": {}}]). The check len(lbcfg) != 1 catches both. Also triggered by malformed JSON object syntax that json.Unmarshal still parses into a multi-key map.

Common situations: Hand-editing service config and accidentally merging two policies into one object; a config builder that emits all policies into a single map instead of separate array entries; misunderstanding the schema (the array-of-single-key-maps shape is non-obvious).

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/52acfcbc4d4f0a3d. Report an issue: GitHub.

Appendix: 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 0c51461d27)