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.

Solutions

  1. Inspect the raw xDS response and fix the malformed Struct on the management server
  2. Verify protobuf/Envoy API dependency versions are consistent (JsonFormat printer behavior differs across versions)
  3. Simplify the custom config to plain JSON-compatible fields and retest
  4. 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

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.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/947936ac07a31a5f. Report an issue: GitHub.

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