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

  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.