{"record":{"id":"58a44bc70195a43e","repo":"grpc/grpc-go","slug":"xds-failed-to-json-unmarshal-server-configuration-58a44b","errorCode":null,"errorMessage":"xds: failed to JSON unmarshal server configuration during bootstrap: %v, config:\n%s","messagePattern":"xds: failed to JSON unmarshal server configuration during bootstrap: (.+?), config:\n(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/xds/bootstrap/bootstrap.go","lineNumber":352,"sourceCode":"\t\tServerURI:        sc.serverURI,\n\t\tChannelCreds:     sc.channelCreds,\n\t\tCallCredsConfigs: sc.callCredsConfigs,\n\t\tServerFeatures:   sc.serverFeatures,\n\t}\n\treturn json.Marshal(server)\n}\n\n// extraDialOptions captures custom dial options specified via\n// credentials.Bundle.\ntype extraDialOptions interface {\n\tDialOptions() []grpc.DialOption\n}\n\n// UnmarshalJSON takes the json data (a server) and unmarshals it to the struct.\nfunc (sc *ServerConfig) UnmarshalJSON(data []byte) error {\n\tserver := serverConfigJSON{}\n\tif err := json.Unmarshal(data, &server); err != nil {\n\t\treturn fmt.Errorf(\"xds: failed to JSON unmarshal server configuration during bootstrap: %v, config:\\n%s\", err, string(data))\n\t}\n\n\tsc.serverURI = server.ServerURI\n\tsc.channelCreds = server.ChannelCreds\n\tsc.callCredsConfigs = server.CallCredsConfigs\n\tsc.serverFeatures = server.ServerFeatures\n\n\tfor _, cc := range server.ChannelCreds {\n\t\t// We stop at the first credential type that we support.\n\t\tc := bootstrap.GetChannelCredentials(cc.Type)\n\t\tif c == nil {\n\t\t\tcontinue\n\t\t}\n\t\tbundle, cancel, err := c.Build(cc.Config)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to build credentials bundle from bootstrap for %q: %v\", cc.Type, err)\n\t\t}\n\t\tsc.selectedChannelCreds = cc","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/bootstrap/bootstrap.go#L334-L370","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the offending server object printed in the error and fix its JSON","Ensure each server has `server_uri` and an array `channel_creds` of {type,config}","Re-validate the whole bootstrap file against the schema"],"exampleFix":"// before\n{\"server_uri\":\"xds.example.com\",\"channel_creds\":\"insecure\"}\n// after\n{\"server_uri\":\"xds.example.com\",\"channel_creds\":[{\"type\":\"insecure\"}]}","handlingStrategy":"validation","validationCode":"var probe struct {\n    XDSServers []struct {\n        ServerURI    string            `json:\"server_uri\"`\n        ChannelCreds []json.RawMessage `json:\"channel_creds\"`\n    } `json:\"xds_servers\"`\n}\n_ = json.Unmarshal(data, &probe)\nfor i, s := range probe.XDSServers {\n    if s.ServerURI == \"\" || len(s.ChannelCreds) == 0 {\n        return fmt.Errorf(\"xds_servers[%d] invalid: missing server_uri or channel_creds\", i)\n    }\n}","typeGuard":null,"tryCatchPattern":"cfg, err := bootstrap.NewConfigFromContents(data)\nif err != nil {\n    log.Fatalf(\"xDS bootstrap server entry invalid: %v\", err)\n}","preventionTips":["Validate each server entry has server_uri and an array channel_creds","Use the same schema check in config templating"],"tags":["grpc","xds","bootstrap","config","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}