grpc/grpc-java · error · ResourceInvalidException
Custom LB config does not contain a JSON object
Error message
Custom LB config does not contain a JSON object
What it means
A custom LB policy config must be a JSON object (map) because it is passed to the named load balancing policy provider as a config map. If printing the Struct to JSON yields a scalar, array, or null instead of an object, the factory rejects the resource with this error.
Source
Thrown at xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java:375
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.
static class MaxRecursionReachedException extends Exception {
static final long serialVersionUID = 1L;
}
}View on GitHub (pinned to 64daddc1f3)
Solutions
- Make the custom LB config a JSON object, e.g. {"field": value}, in the policy's custom_config on the control plane
- Confirm the policy name is set in the oneof name field, not inside the Struct
- Validate the emitted CDS/LDS JSON with an Envoy config dump before deploying
Example fix
// before (custom config as bare value)
"custom_config": "my-policy"
// after
"custom_config": {"someField": "someValue"} Defensive patterns
Strategy: validation
Validate before calling
// Ensure the custom config value is a JSON object before sending:
if (!(jsonNode.isObject())) {
throw new IllegalArgumentException("custom_config must be a JSON object");
} Try / catch
try {
Map<String, ?> cfg = (Map<String, ?>) parseCustomConfigJson(struct);
} catch (ResourceInvalidException | ClassCastException e) {
logger.warning("Custom LB config must be an object: " + e.getMessage());
} Prevention
- Always wrap custom policy config in an object: {"key": value}
- Put the policy name in the policy's name oneof, never inside the Struct
- Validate xDS JSON with Envoy's config dump tooling
When it happens
Trigger: parseCustomConfigJson successfully parses the Struct's JSON but the result is not a java.util.Map — e.g. the custom config's Struct contains a single non-object value (string, number, list) instead of fields.
Common situations: Hand-written xDS test fixtures putting a bare value in custom_config; a control plane serializing the wrong field into custom config; user confusion between policy name and policy config (putting the name string where the config object belongs).
Related errors
- Unable to parse custom LB config JSON
- unsupported ExtAuthz service type: only grpc_service is supp
- Invalid ring hash function: " + ringHash.getHashFunction()
- Invalid header matcher config: [grpc-] prefixed header name
- Invalid header matcher config: header name [:scheme] is not
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/88a15dbadc459767.
Report an issue: GitHub.