grpc/grpc-java · error · ResourceInvalidException
Unable to parse custom LB config JSON
Error message
Unable to parse custom LB config JSON
What it means
Custom (third-party) LB policy configs arrive in the xDS proto as a google.protobuf.Struct. The factory prints the Struct back to JSON and parses it with grpc's JsonParser; if printing/parsing throws an IOException, the config cannot be turned into the JSON object form the LB provider expects, so a ResourceInvalidException is thrown.
Source
Thrown at xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java:371
*/
@SuppressWarnings("unchecked")
private static ImmutableMap<String, ?> convertCustomConfig(
com.github.udpa.udpa.type.v1.TypedStruct configTypedStruct)
throws ResourceInvalidException {
return ImmutableMap.of(parseCustomConfigTypeName(configTypedStruct.getTypeUrl()),
(Map<String, ?>) parseCustomConfigJson(configTypedStruct.getValue()));
}
/**
* Print the config Struct into JSON and then parse that into our internal representation.
*/
private static Object parseCustomConfigJson(Struct configStruct)
throws ResourceInvalidException {
Object rawJsonConfig = null;
try {
rawJsonConfig = JsonParser.parse(JsonFormat.printer().print(configStruct));
} catch (IOException e) {
throw new ResourceInvalidException("Unable to parse custom LB config JSON", e);
}
if (!(rawJsonConfig instanceof Map)) {
throw new ResourceInvalidException("Custom LB config does not contain a JSON object");
}
return rawJsonConfig;
}
private static String parseCustomConfigTypeName(String customConfigTypeName) {
if (customConfigTypeName.contains("/")) {
customConfigTypeName = customConfigTypeName.substring(
customConfigTypeName.lastIndexOf("/") + 1);
}
return customConfigTypeName;
}
// Used to signal that the LB config goes too deep.View on GitHub (pinned to 64daddc1f3)
Solutions
- Inspect the raw xDS response and fix the malformed Struct on the management server
- Verify protobuf/Envoy API dependency versions are consistent (JsonFormat printer behavior differs across versions)
- Simplify the custom config to plain JSON-compatible fields and retest
- Check grpc xDS debug logs for the wrapped IOException cause for the exact offending value
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
// Sanity-check the custom config is printable JSON before sending: JsonFormat.printer().printingEscapeNonAscii(true).print(struct); // throws if malformed
Try / catch
try {
Object cfg = parseCustomConfigJson(struct);
} catch (ResourceInvalidException e) {
logger.warning("Custom LB config rejected: " + e.getMessage());
// treat resource as invalid, fall back to a known-good LB policy
} Prevention
- Keep custom LB configs as plain JSON-compatible Struct fields
- Avoid mixed proto/JSON tooling versions on the control plane
- Log raw ADS responses to catch malformed Structs early
When it happens
Trigger: convertCustomConfig calls parseCustomConfigJson with a Struct that JsonFormat.printer().print() cannot serialize or JsonParser.parse() cannot parse — typically a Struct containing values incompatible with the JSON printer (e.g. invalid protobuf value types).
Common situations: A control plane sending a malformed custom LB policy Struct; proto library version mismatch where the Struct contains value types the printer rejects; corrupted ADS response.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Custom LB config does not contain a JSON object
- Failed to parse JSON
- Not implemented
- Authorization policy should be a JSON object. Found: null
- "name" is absent or empty
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/947936ac07a31a5f.
Report an issue: GitHub.