{"record":{"id":"88a15dbadc459767","repo":"grpc/grpc-java","slug":"custom-lb-config-does-not-contain-a-json-object","errorCode":null,"errorMessage":"Custom LB config does not contain a JSON object","messagePattern":"Custom LB config does not contain a JSON object","errorType":"validation","errorClass":"ResourceInvalidException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java","lineNumber":375,"sourceCode":"        throws ResourceInvalidException {\n      return ImmutableMap.of(parseCustomConfigTypeName(configTypedStruct.getTypeUrl()),\n          (Map<String, ?>) parseCustomConfigJson(configTypedStruct.getValue()));\n    }\n\n    /**\n     * Print the config Struct into JSON and then parse that into our internal representation.\n     */\n    private static Object parseCustomConfigJson(Struct configStruct)\n        throws ResourceInvalidException {\n      Object rawJsonConfig = null;\n      try {\n        rawJsonConfig = JsonParser.parse(JsonFormat.printer().print(configStruct));\n      } catch (IOException e) {\n        throw new ResourceInvalidException(\"Unable to parse custom LB config JSON\", e);\n      }\n\n      if (!(rawJsonConfig instanceof Map)) {\n        throw new ResourceInvalidException(\"Custom LB config does not contain a JSON object\");\n      }\n      return rawJsonConfig;\n    }\n\n\n    private static String parseCustomConfigTypeName(String customConfigTypeName) {\n      if (customConfigTypeName.contains(\"/\")) {\n        customConfigTypeName = customConfigTypeName.substring(\n            customConfigTypeName.lastIndexOf(\"/\") + 1);\n      }\n      return customConfigTypeName;\n    }\n\n    // Used to signal that the LB config goes too deep.\n    static class MaxRecursionReachedException extends Exception {\n      static final long serialVersionUID = 1L;\n    }\n  }","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java#L357-L393","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before (custom config as bare value)\n\"custom_config\": \"my-policy\"\n// after\n\"custom_config\": {\"someField\": \"someValue\"}","handlingStrategy":"validation","validationCode":"// Ensure the custom config value is a JSON object before sending:\nif (!(jsonNode.isObject())) {\n  throw new IllegalArgumentException(\"custom_config must be a JSON object\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  Map<String, ?> cfg = (Map<String, ?>) parseCustomConfigJson(struct);\n} catch (ResourceInvalidException | ClassCastException e) {\n  logger.warning(\"Custom LB config must be an object: \" + e.getMessage());\n}","preventionTips":["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"],"tags":["grpc","xds","json","custom-lb-policy","config-validation"],"backgroundTag":"unexpected-response-shape","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}