grpc/grpc-go · critical

xds: failed to JSON unmarshal server configuration during bo

Error message

xds: failed to JSON unmarshal server configuration during bootstrap: %v, config:
%s

What it means

Returned by ServerConfig.UnmarshalJSON (internal/xds/bootstrap/bootstrap.go:351) when a single server object inside `xds_servers` fails to unmarshal into serverConfigJSON. This is the per-entry variant of the bootstrap unmarshal failure and is fatal to bootstrap.

Source

Thrown at internal/xds/bootstrap/bootstrap.go:352

		ServerURI:        sc.serverURI,
		ChannelCreds:     sc.channelCreds,
		CallCredsConfigs: sc.callCredsConfigs,
		ServerFeatures:   sc.serverFeatures,
	}
	return json.Marshal(server)
}

// extraDialOptions captures custom dial options specified via
// credentials.Bundle.
type extraDialOptions interface {
	DialOptions() []grpc.DialOption
}

// UnmarshalJSON takes the json data (a server) and unmarshals it to the struct.
func (sc *ServerConfig) UnmarshalJSON(data []byte) error {
	server := serverConfigJSON{}
	if err := json.Unmarshal(data, &server); err != nil {
		return fmt.Errorf("xds: failed to JSON unmarshal server configuration during bootstrap: %v, config:\n%s", err, string(data))
	}

	sc.serverURI = server.ServerURI
	sc.channelCreds = server.ChannelCreds
	sc.callCredsConfigs = server.CallCredsConfigs
	sc.serverFeatures = server.ServerFeatures

	for _, cc := range server.ChannelCreds {
		// We stop at the first credential type that we support.
		c := bootstrap.GetChannelCredentials(cc.Type)
		if c == nil {
			continue
		}
		bundle, cancel, err := c.Build(cc.Config)
		if err != nil {
			return fmt.Errorf("failed to build credentials bundle from bootstrap for %q: %v", cc.Type, err)
		}
		sc.selectedChannelCreds = cc

View on GitHub (pinned to 03255a9237)

Solutions

  1. Inspect the offending server object printed in the error and fix its JSON
  2. Ensure each server has `server_uri` and an array `channel_creds` of {type,config}
  3. Re-validate the whole bootstrap file against the schema

Example fix

// before
{"server_uri":"xds.example.com","channel_creds":"insecure"}
// after
{"server_uri":"xds.example.com","channel_creds":[{"type":"insecure"}]}
Defensive patterns

Strategy: validation

Validate before calling

var probe struct {
    XDSServers []struct {
        ServerURI    string            `json:"server_uri"`
        ChannelCreds []json.RawMessage `json:"channel_creds"`
    } `json:"xds_servers"`
}
_ = json.Unmarshal(data, &probe)
for i, s := range probe.XDSServers {
    if s.ServerURI == "" || len(s.ChannelCreds) == 0 {
        return fmt.Errorf("xds_servers[%d] invalid: missing server_uri or channel_creds", i)
    }
}

Try / catch

cfg, err := bootstrap.NewConfigFromContents(data)
if err != nil {
    log.Fatalf("xDS bootstrap server entry invalid: %v", err)
}

Prevention

When it happens

Trigger: One element of the `xds_servers` array is not a valid server object — e.g. a JSON syntax error within that entry, an unexpected field type, or a malformed `channel_creds`/`call_creds` sub-object.

Common situations: A specific server entry has a typo (e.g. `channel_creds` set to a string instead of an array); partial/corrupt bootstrap file; mismatched schema version for one server.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/58a44bc70195a43e. Report an issue: GitHub.