{"record":{"id":"98751246b255ab26","repo":"apache/druid","slug":"jsonconfigurator-requires-jackson-annotated-config","errorCode":null,"errorMessage":"JsonConfigurator requires Jackson-annotated Config objects to have field annotations. %s doesn't","messagePattern":"JsonConfigurator requires Jackson-annotated Config objects to have field annotations\\. (.+?) doesn't","errorType":"validation","errorClass":"ProvisionException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/guice/JsonConfigurator.java","lineNumber":290,"sourceCode":"        }\n      }\n      catch (NoSuchMethodException e) {\n        throw new ProvisionException(\n            StringUtils.format(\n                \"JsonConfigurator requires default classes to have zero-arg constructors. %s doesn't\",\n                defaultClass\n            )\n        );\n      }\n    }\n\n    final List<BeanPropertyDefinition> beanDefs = mapper.getSerializationConfig()\n                                                        .introspect(mapper.constructType(clazz))\n                                                        .findProperties();\n    for (BeanPropertyDefinition beanDef : beanDefs) {\n      final AnnotatedField field = beanDef.getField();\n      if (field == null || !field.hasAnnotation(JsonProperty.class)) {\n        throw new ProvisionException(\n            StringUtils.format(\n                \"JsonConfigurator requires Jackson-annotated Config objects to have field annotations. %s doesn't\",\n                clazz\n            )\n        );\n      }\n    }\n  }\n\n  private static <T> Optional<Method> findJsonCreatorFactoryMethod(Class<T> clazz)\n  {\n    return Arrays.stream(clazz.getMethods())\n                 .filter(m -> m.getAnnotation(JsonCreator.class) != null\n                              && m.getParameterCount() == 0\n                              && m.getReturnType()\n                                  .equals(clazz))\n                 .findFirst();\n","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/guice/JsonConfigurator.java#L272-L308","documentation":"JsonConfigurator requires that every property of a config class be Jackson-deserializable from runtime properties, and it enforces that each serialized bean property has a backing field annotated with @JsonProperty. If any introspected property lacks a field or the field lacks @JsonProperty, verifyClazzIsConfigurable throws this ProvisionException at injector startup.","triggerScenarios":"configurate() calls verifyClazzIsConfigurable; Jackson introspection finds a BeanPropertyDefinition whose getField() is null (getter-only property) or whose AnnotatedField.hasAnnotation(JsonProperty.class) is false.","commonSituations":"Config class uses getter/setter style without @JsonProperty on fields; a derived/read-only getter property with no backing field; Lombok or code-generated classes without Jackson annotations; migrating a POJO from another framework.","solutions":["Annotate every field of the config class with @JsonProperty","Add fields backing any getter-only properties, or remove the getter from the config class","Use a @JsonCreator/@JsonProperty constructor if field annotation is impossible, per the class's other requirements","Keep config classes as simple, Jackson-annotated POJOs per Druid conventions"],"exampleFix":"// before\npublic class MyConfig {\n  private String name;\n  public String getName() { return name; }\n}\n// after\npublic class MyConfig {\n  @JsonProperty public String name;\n}","handlingStrategy":"validation","validationCode":"for (Field f : clazz.getDeclaredFields()) {\n  if (!f.isAnnotationPresent(JsonProperty.class)) throw new IllegalStateException(\"Missing @JsonProperty: \" + f);\n}","typeGuard":"boolean hasFieldAnnotations(Class<?> c) {\n  return new ObjectMapper().getSerializationConfig().introspect(new ObjectMapper().constructType(c))\n      .findProperties().stream().allMatch(p -> p.getField() != null && p.getField().hasAnnotation(JsonProperty.class));\n}","tryCatchPattern":"try { injector.getInstance(configKey); } catch (ProvisionException e) { if (e.getMessage().contains(\"field annotations\")) log.error(\"Annotate config fields with @JsonProperty\"); throw e; }","preventionTips":["Convention: every field in a config class gets @JsonProperty","Avoid getter-only derived properties in config classes","Run a reflection-based unit test that verifies all extension config classes comply"],"tags":["guice","jackson","configuration","startup"],"backgroundTag":"schema-validation-failed","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}