How would you create a custom annotation that contributes your own reflection hints via a ReflectiveProcessor?
answer
- implement ReflectiveProcessor OR extend SimpleReflectiveProcessor
- annotation @Reflective(MyProcessor.class) + @Retention(RUNTIME)
- registerType with chosen MemberCategory set
- scanned by ReflectiveProcessorBeanFactoryInitializationAotProcessor
- bean-scoped; verify with RuntimeHintsPredicates
basics
~10 sWrite a ReflectiveProcessor implementing registerReflectionHints, then create your own annotation meta-annotated with @Reflective(YourProcessor.class). Put your annotation on beans/members; AOT scanning invokes the processor and emits the hints.
solid answer
~40 sTwo pieces. First, implement ReflectiveProcessor: in registerReflectionHints(ReflectionHints hints, AnnotatedElement element) inspect the element and call hints.registerType(...), registerMethod(...), registerConstructor(...), or registerField(...), choosing MemberCategory / ExecutableMode values for the access you need. You can extend SimpleReflectiveProcessor and override just its protected per-element hooks. Second, define a custom annotation meta-annotated @Reflective(MyProcessor.class) with a suitable @Target/@Retention(RUNTIME). Then annotate your bean types/methods with it. During AOT, ReflectiveProcessorBeanFactoryInitializationAotProcessor scans registered beans, finds elements carrying your annotation, and runs your processor to emit hints into the generated metadata. This is exactly how Spring's own @RegisterReflectionForBinding works. Because scanning is bean-scoped, the annotated element must belong to a reachable bean, and everything happens at build time.
code
java · 22 lines// 1) Processor
public class EntityReflectiveProcessor implements ReflectiveProcessor {
@Override
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
if (element instanceof Class<?> type) {
hints.registerType(type,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.DECLARED_FIELDS);
}
}
}
// 2) Meta-annotated marker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Reflective(EntityReflectiveProcessor.class)
public @interface ReflectiveEntity { }
// 3) Applied to a bean -> AOT emits the hints
@Component
@ReflectiveEntity
public class OrderEntity { /* ... */ }go deeper
Know a custom annotation can be tied to a processor, but likely won't implement one.
Sketch the annotation + processor pair and where hints go.
Choose MemberCategory/ExecutableMode correctly, extend SimpleReflectiveProcessor, and verify with RuntimeHintsPredicates.
Weigh bean-scoped scanning vs RuntimeHintsRegistrar, metadata-size trade-offs, and testing/verification strategy for hint correctness.
## Recipe ### 1. Implement a ReflectiveProcessor ```java public class EntityReflectiveProcessor implements ReflectiveProcessor { @Override public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { if (element instanceof Class<?> type) { hints.registerType(type, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS); } } } ``` - **`ReflectionHints`** is the sink. Its `registerType(Class<?>, MemberCategory...)` opens categories of members. - **`MemberCategory`** (`org.springframework.aot.hint`) enumerates what to allow: e.g. `DECLARED_FIELDS`, `INVOKE_DECLARED_CONSTRUCTORS`, `INVOKE_PUBLIC_METHODS`, `INTROSPECT_DECLARED_METHODS`, etc. Pick the minimum you actually need — broad categories bloat the image and metadata. - Alternatively **extend `SimpleReflectiveProcessor`** and override `registerTypeHint`/`registerMethodHint`/... to keep default behaviour for some element kinds while broadening others. ### 2. Define the meta-annotated annotation ```java @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Reflective(EntityReflectiveProcessor.class) public @interface ReflectiveEntity { } ``` `@Retention(RUNTIME)` is required so the annotation is discoverable. `@Target` limits where it can go. `@Reflective` names your processor. ### 3. Use it on beans ```java @Component @ReflectiveEntity public class OrderEntity { /* fields, ctors, methods */ } ``` ## How it runs At build time, `org.springframework.context.aot.ReflectiveProcessorBeanFactoryInitializationAotProcessor` (a `BeanFactoryInitializationAotProcessor`) iterates registered bean classes and, via `ReflectiveRuntimeHintsRegistrar`, finds elements meta-annotated with `@Reflective`. For each, it instantiates and calls your `ReflectiveProcessor`. The emitted `ReflectionHints` land in the AOT-generated reachability metadata compiled into the native image. ## Gotchas - **Bean-scoped scanning**: only beans and their reachable members are scanned. Annotating an arbitrary utility class that Spring never registers yields no hints. For non-bean types, prefer a `RuntimeHintsRegistrar` + `@ImportRuntimeHints`, or list them in a processor. - **Processor instantiation**: needs a no-arg constructor; keep it stateless/cheap — it runs at build time. - **Choose categories deliberately**: over-registering hurts binary size; under-registering causes runtime `NoSuchMethodException`/binding failures visible only in native. - **JVM inertness**: nothing changes on the JVM; test in a native image (or with `RuntimeHintsPredicates` in an AOT unit test) to verify hints. - **AliasFor / attributes**: you can add attributes to your annotation (like `classes()` on the binding one) and read them off the `AnnotatedElement` inside the processor with `MergedAnnotations`/`AnnotatedElementUtils`.
- Your custom annotation is on a plain POJO that isn't a Spring bean and no hints appear. Why?The @Reflective scanner is bean-scoped — it walks registered bean classes and their reachable members, not the whole classpath. For non-bean types register hints imperatively via a RuntimeHintsRegistrar imported with @ImportRuntimeHints, or list them explicitly in a processor.
- How can you unit-test that the hints were registered without building a native image?Run the RuntimeHints generation and assert with RuntimeHintsPredicates (e.g. RuntimeHintsPredicates.reflection().onType(...).withMemberCategory(...)) against the produced RuntimeHints — no native compilation needed.
saying these in an interview costs you the question
- Forgetting @Retention(RUNTIME) so the annotation is invisible to scanning.
- Expecting non-bean classes to be scanned.
- Registering the broadest MemberCategory set 'to be safe', bloating the image.
- Putting mutable/runtime state in the processor as if it runs at request time.