skip to content

What is the @Reflective meta-annotation and what problem does it solve for GraalVM native images?

level: juniorimportance: should knowfreq 25%

answer

  1. closed-world native image drops reflection
  2. meta-annotation -> auto reflection hints
  3. value() = ReflectiveProcessor, default SimpleReflectiveProcessor
  4. scanned at AOT build time
  5. org.springframework.aot.hint.annotation

basics

~20 s

GraalVM native image drops reflection metadata it can't see at build time. @Reflective is a Spring meta-annotation you put on another annotation so that, during AOT, Spring automatically registers reflection hints for every element marked with it.

solid answer

~40 s

A GraalVM native image is built with closed-world analysis: the compiler statically decides what code and metadata to keep, and reflective access that isn't declared up front fails at runtime. Spring bridges this with AOT-generated 'reflection hints'. @Reflective (in org.springframework.aot.hint.annotation) is a meta-annotation: when a custom or built-in annotation is itself annotated with @Reflective, Spring's AOT engine scans beans for elements (types, methods, fields, constructors) carrying that annotation and contributes the matching reflection hints automatically. @Reflective names one or more ReflectiveProcessor implementations that decide exactly which hints to emit; the default is SimpleReflectiveProcessor. This is the low-level mechanism behind higher-level conveniences like @RegisterReflectionForBinding. It removes the need to hand-write a RuntimeHintsRegistrar for annotation-driven cases.

code

java · 11 lines
java
// A built-in Spring example: @RegisterReflectionForBinding is meta-annotated
// with @Reflective, which is what makes AOT register hints for it.
package org.springframework.aot.hint.annotation;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Reflective(RegisterReflectionForBindingProcessor.class) // <-- the mechanism
public @interface RegisterReflectionForBinding {
    @AliasFor("classes") Class<?>[] value() default {};
    @AliasFor("value") Class<?>[] classes() default {};
}

go deeper

for a junior

Know that native images need reflection declared up front and @Reflective automates that for annotated elements.

for a middle

Explain that @Reflective points at a ReflectiveProcessor and is scanned during AOT over beans.

for a senior

Connect it to RuntimeHints/ReflectionHints and name it as the engine behind @RegisterReflectionForBinding.

for a principal

Discuss bean-scoped scanning, the BeanFactoryInitializationAotProcessor, and build-time vs runtime failure modes.

## Background: why hints exist When you compile a Spring app to a GraalVM **native image**, GraalVM performs **closed-world / ahead-of-time (AOT) analysis**: it statically walks all reachable code at build time and discards anything it can't prove is used. **Reflection** (looking up methods/fields/constructors by name at runtime via `java.lang.reflect`) is opaque to that analysis, so any reflective access must be *declared in advance*. GraalVM reads this from `reachability-metadata`/`reflect-config.json` files. Spring generates those files during its **AOT processing phase** by producing `RuntimeHints` — specifically `ReflectionHints`. ## What @Reflective is `@Reflective` is a **meta-annotation** in package `org.springframework.aot.hint.annotation`. You don't usually put it directly on a class; you put it on *another annotation*. Any annotation meta-annotated with `@Reflective` becomes a signal: 'whenever Spring's AOT engine finds an element (a type, constructor, method, or field of a bean) annotated with me, register reflection hints for that element.' Its single attribute is: ```java Class<? extends ReflectiveProcessor>[] value() default SimpleReflectiveProcessor.class; ``` So `@Reflective` points at one or more **`ReflectiveProcessor`** implementations that do the actual hint registration. If you don't name one, `SimpleReflectiveProcessor` is used. ## How scanning happens At build time a `BeanFactoryInitializationAotProcessor` — `org.springframework.context.aot.ReflectiveProcessorBeanFactoryInitializationAotProcessor` — iterates the registered bean classes and uses `ReflectiveRuntimeHintsRegistrar` to look for `@Reflective`-meta-annotated annotations on each type and its members. For every match it invokes the referenced `ReflectiveProcessor`, which writes entries into `ReflectionHints`. Those become the generated reflection metadata. ## Why it matters Without hints, a JVM app runs fine (JIT, full reflection), but the native image throws `ClassNotFoundException`, `NoSuchMethodException`, or silently missing beans/serialization. `@Reflective` gives library and framework authors a declarative, annotation-driven way to attach reflection hints instead of writing an imperative `RuntimeHintsRegistrar`. ## Gotchas - Hints only affect **native image**; on the JVM they're inert. - Scanning is **bean-scoped** — the element must belong to a class Spring knows about as a bean (or reachable member), not any arbitrary classpath type. - It runs at **build time**; a wrong or missing hint only shows up when you actually run the native binary.

  • Do these hints do anything when running on a normal JVM?
    No. On the JVM reflection is unrestricted, so the hints are ignored. They only matter when producing a GraalVM native image, where reflection metadata must be declared at build time.

saying these in an interview costs you the question

  • Thinking @Reflective affects runtime behaviour on the plain JVM.
  • Believing it scans the entire classpath rather than Spring beans.
  • Confusing reflection hints with dependency injection annotations.

context