{"record":{"id":"a8eb0d9cc863ae1a","repo":"grpc/grpc-java","slug":"either-ca-certificate-file-or-spiffe-trust-bund","errorCode":null,"errorMessage":"either 'ca_certificate_file' or 'spiffe_trust_bundle_map_file' is required in the config","messagePattern":"either 'ca_certificate_file' or 'spiffe_trust_bundle_map_file' is required in the config","errorType":"validation","errorClass":"NullPointerException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java","lineNumber":110,"sourceCode":"        configObj.refrehInterval,\n        scheduledExecutorServiceFactory.create(),\n        timeProvider);\n  }\n\n  private static String checkForNullAndGet(Map<String, ?> map, String key) {\n    return checkNotNull(JsonUtil.getString(map, key), \"'\" + key + \"' is required in the config\");\n  }\n\n  private static Config validateAndTranslateConfig(Object config) {\n    checkArgument(config instanceof Map, \"Only Map supported for config\");\n    @SuppressWarnings(\"unchecked\") Map<String, ?> map = (Map<String, ?>)config;\n\n    Config configObj = new Config();\n    configObj.certFile = checkForNullAndGet(map, CERT_FILE_KEY);\n    configObj.keyFile = checkForNullAndGet(map, KEY_FILE_KEY);\n    if (enableSpiffe) {\n      if (!map.containsKey(ROOT_FILE_KEY) && !map.containsKey(SPIFFE_TRUST_MAP_FILE_KEY)) {\n        throw new NullPointerException(\n            String.format(\"either '%s' or '%s' is required in the config\",\n                ROOT_FILE_KEY, SPIFFE_TRUST_MAP_FILE_KEY));\n      }\n      if (map.containsKey(SPIFFE_TRUST_MAP_FILE_KEY)) {\n        configObj.spiffeTrustMapFile = JsonUtil.getString(map, SPIFFE_TRUST_MAP_FILE_KEY);\n      } else {\n        configObj.rootFile = JsonUtil.getString(map, ROOT_FILE_KEY);\n      }\n    } else {\n      configObj.rootFile = checkForNullAndGet(map, ROOT_FILE_KEY);\n    }\n    String refreshIntervalString = JsonUtil.getString(map, REFRESH_INTERVAL_KEY);\n    if (refreshIntervalString != null) {\n      try {\n        Duration duration = Durations.parse(refreshIntervalString);\n        configObj.refrehInterval = duration.getSeconds();\n        checkArgument(configObj.refrehInterval > 0L, \"refreshInterval needs to be greater than 0\");\n      } catch (ParseException e) {","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java#L92-L128","documentation":"FileWatcherCertificateProviderProvider.validateAndTranslateConfig validates the JSON config for the file_watcher certificate provider. When SPIFFE support is enabled, the config must supply at least one trust anchor source: a root CA certificate file ('ca_certificate_file') or a SPIFFE trust bundle map file ('spiffe_trust_bundle_map_file'). If neither key is present, a NullPointerException with this message is thrown (misleading exception type, but it signals a missing required config field).","triggerScenarios":"Enabling SPIFFE and registering a file_watcher provider whose config JSON contains certificate_file and private_key_file but lacks both 'ca_certificate_file' and 'spiffe_trust_bundle_map_file'; thrown from validateAndTranslateConfig during config parsing (called via configObj).","commonSituations":"Cert configs copied from non-SPIFFE examples missing trust roots; bootstrap certProviders entries where only client cert/key files were specified; migration to SPIFFE trust bundles where the root file key was renamed.","solutions":["Add 'ca_certificate_file': '<path to root CA pem>' to the file_watcher provider config","Alternatively add 'spiffe_trust_bundle_map_file': '<path to trust bundle JSON>' for SPIFFE trust domains","Verify the bootstrap certProviders JSON contains one of the two keys when SPIFFE is enabled","Fix the control plane or deployment templates that generate the provider config to always include a trust anchor"],"exampleFix":"// before\n{\"certificate_file\": \"cert.pem\", \"private_key_file\": \"key.pem\"}\n// after\n{\"certificate_file\": \"cert.pem\", \"private_key_file\": \"key.pem\", \"ca_certificate_file\": \"ca.pem\"}","handlingStrategy":"validation","validationCode":"// Validate file_watcher provider config JSON before registration\nboolean validTrustSource(Map<String, ?> map) {\n  return map.containsKey(\"ca_certificate_file\") || map.containsKey(\"spiffe_trust_bundle_map_file\");\n}\nif (!validTrustSource(config)) throw new IllegalArgumentException(\"file_watcher config needs ca_certificate_file or spiffe_trust_bundle_map_file\");","typeGuard":null,"tryCatchPattern":"try {\n  registry.register(fileWatcherProvider);\n} catch (NullPointerException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"is required in the config\")) {\n    logger.error(\"file_watcher config missing trust anchor: add ca_certificate_file or spiffe_trust_bundle_map_file\", e);\n  }\n  throw e;\n}","preventionTips":["Always include a trust anchor key in file_watcher configs when SPIFFE is enabled","Lint bootstrap certProviders JSON in CI for required keys","Keep deployment templates in sync with the provider's expected config schema"],"tags":["xds","certificate-provider","configuration","spiffe"],"backgroundTag":"missing-required-config-field","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"}