{"record":{"id":"e64f6cff1245ffb2","repo":"grpc/grpc-go","slug":"xds-error-normalizing-json-bootstrap-configuratio","errorCode":null,"errorMessage":"xds: error normalizing JSON bootstrap configuration: %v","messagePattern":"xds: error normalizing JSON bootstrap configuration: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/bootstrap/bootstrap.go","lineNumber":694,"sourceCode":"\n\tif fContent != \"\" {\n\t\tif logger.V(2) {\n\t\t\tlogger.Infof(\"Using bootstrap contents from GRPC_XDS_BOOTSTRAP_CONFIG environment variable\")\n\t\t}\n\t\treturn NewConfigFromContents([]byte(fContent))\n\t}\n\n\treturn nil, nil\n}\n\n// NewConfigFromContents creates a new bootstrap configuration from the provided\n// contents.\nfunc NewConfigFromContents(data []byte) (*Config, error) {\n\t// Normalize the input configuration.\n\tbuf := bytes.Buffer{}\n\terr := json.Indent(&buf, data, \"\", \"\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"xds: error normalizing JSON bootstrap configuration: %v\", err)\n\t}\n\tdata = bytes.TrimSpace(buf.Bytes())\n\n\tconfig := &Config{}\n\tif err := config.UnmarshalJSON(data); err != nil {\n\t\treturn nil, err\n\t}\n\treturn config, nil\n}\n\n// ConfigOptionsForTesting specifies options for creating a new bootstrap\n// configuration for testing purposes.\n//\n// # Testing-Only\ntype ConfigOptionsForTesting struct {\n\t// Servers is the top-level xDS server configuration. It contains a list of\n\t// server configurations.\n\tServers json.RawMessage","sourceCodeStart":676,"sourceCodeEnd":712,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/bootstrap/bootstrap.go#L676-L712","documentation":"NewConfigFromContents first runs json.Indent on the raw bytes to normalize whitespace (bootstrap.go:692-694). json.Indent requires well-formed JSON input; if the bytes are not valid JSON it returns an error that is wrapped here. This is an earlier, stricter check than UnmarshalJSON and catches malformed input before the structured parse even begins.","triggerScenarios":"Calling NewConfigFromContents (or GetConfiguration via GRPC_XDS_BOOTSTRAP_CONFIG) with bytes that are not valid JSON — empty bytes, a YAML/markup document, a protobuf binary, or truncated JSON. json.Indent fails before any field-level parsing.","commonSituations":"Passing a YAML bootstrap where JSON was expected; an env var contains a base64 or otherwise encoded blob that was not decoded first; the content was truncated by a shell or config-map size limit.","solutions":["Confirm the input is JSON by piping it through jq or a JSON parser.","If you have YAML, convert it to JSON before setting GRPC_XDS_BOOTSTRAP_CONFIG or writing the file.","Ensure the full content is present — check for truncation in env var injection or ConfigMap size limits.","For empty input, note that GetConfiguration returns (nil, nil) when neither env var is set, so this error specifically means a non-empty but invalid value."],"exampleFix":"// before: GRPC_XDS_BOOTSTRAP_CONFIG contains YAML\n//   xds_servers:\n//     - server_uri: dns:///xds:443\n// after: provide valid JSON\n//   {\"xds_servers\":[{\"server_uri\":\"dns:///xds:443\"}]}","handlingStrategy":"validation","validationCode":"// Confirm the contents are JSON before NewConfigFromContents.\nfunc isValidJSON(data []byte) error {\n    var buf bytes.Buffer\n    if err := json.Indent(&buf, data, \"\", \"\"); err != nil {\n        return fmt.Errorf(\"not valid JSON: %w\", err)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never pass YAML into GRPC_XDS_BOOTSTRAP_CONFIG; convert to JSON first.","Check ConfigMap/secret size limits that could truncate inline content.","Validate inline bootstrap content in CI before injecting it as an env var."],"tags":["xds","bootstrap","json","config","grpc"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}