How do you register reflection hints with ReflectionHints, and what does MemberCategory control?
answer
- reflection() → reflect-config.json
- registerType(Class, MemberCategory...)
- No category = type visible, members not
- MemberCategory: INVOKE_* vs INTROSPECT_*, DECLARED vs PUBLIC
- jni() reuses ReflectionHints, different file
basics
~10 sUse hints.reflection().registerType(MyType.class, category...). MemberCategory selects which members become reflectively accessible — e.g. declared constructors, public/declared methods, or fields. It maps to reflect-config.json.
solid answer
~40 s`ReflectionHints` (via `hints.reflection()`) declares which types can be reflected on in a native image, and how much. You call `registerType(Class, MemberCategory...)` or `registerType(TypeReference, ...)`. The `MemberCategory` enum picks the granularity: `INVOKE_DECLARED_CONSTRUCTORS`, `INVOKE_PUBLIC_METHODS`, `INVOKE_DECLARED_METHODS`, `DECLARED_FIELDS`, `PUBLIC_FIELDS`, and 'introspection' variants that allow discovery without invocation. Registering a type with no categories still makes the type itself visible (so `Class.forName` succeeds) but not its members. You can also register individual `Field`/`Method`/`Constructor` for finer scope, and `registerTypeIfPresent` to guard on presence. All of this is serialized to `reflect-config.json` (or the consolidated reachability metadata). This is by far the most commonly needed hint — for DTOs bound via Jackson, JPA entities, and anything loaded by name.
code
java · 15 linesimport org.springframework.aot.hint.*;
public class DtoHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader cl) {
hints.reflection().registerType(OrderDto.class,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, // must-have to instantiate
MemberCategory.INVOKE_PUBLIC_METHODS, // getters/setters for binding
MemberCategory.DECLARED_FIELDS);
// Register by name when the class may be absent at build time
hints.reflection().registerTypeIfPresent(cl, "com.acme.LegacyCodec",
MemberCategory.INVOKE_DECLARED_METHODS);
}
}go deeper
Know you call hints.reflection().registerType and that it enables reflection in native images.
Explain MemberCategory granularity and that missing a constructor category breaks instantiation; know it maps to reflect-config.json.
Discuss ExecutableMode, register-by-TypeReference, conditional registration, and the 6.2 category simplification.
Reason about image-size vs coverage tradeoffs, generics, and strategy for reflection in third-party libs without metadata.
## What it is `org.springframework.aot.hint.ReflectionHints`, reached via `RuntimeHints.reflection()`, is the registry for **reflective access**. In a native image, reflection on a type only works if that type — and the specific members you touch — were declared at build time. `ReflectionHints` is how Spring records those declarations; they end up in GraalVM's `reflect-config.json`. ## Registering a type The primary method: ```java hints.reflection().registerType(MyDto.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS); ``` You can also register by name (useful when the class isn't on the build classpath or you want to avoid loading it): ```java hints.reflection().registerType(TypeReference.of("com.acme.Legacy"), MemberCategory.DECLARED_FIELDS); ``` and conditionally: `registerTypeIfPresent(classLoader, "com.acme.Optional", MemberCategory.INVOKE_DECLARED_METHODS)`. Registering a type with **no** `MemberCategory` still makes the *type* reachable — `Class.forName("...")` and `Class` metadata queries succeed — but you cannot yet enumerate or invoke its members. ## `MemberCategory` — the granularity dial `MemberCategory` decides *which members* become reflectively usable and whether you can just *introspect* them or actually *invoke* them: - `INVOKE_DECLARED_CONSTRUCTORS` / `INVOKE_PUBLIC_CONSTRUCTORS` — allow calling constructors (needed for deserialization/instantiation) - `INVOKE_DECLARED_METHODS` / `INVOKE_PUBLIC_METHODS` — allow invoking methods (e.g. getters/setters for binding) - `DECLARED_FIELDS` / `PUBLIC_FIELDS` — allow reading/writing fields - `INTROSPECT_*` variants (e.g. `INTROSPECT_DECLARED_METHODS`) — allow *discovering* members (getMethods) without invoking them 'Declared' = all members including private/protected on that class only; 'Public' = public members including inherited. Broad categories are convenient but bloat the config and the image; prefer narrow ones or register specific members: ```java hints.reflection().registerMethod(myMethod, ExecutableMode.INVOKE); hints.reflection().registerField(myField); hints.reflection().registerConstructor(myCtor, ExecutableMode.INVOKE); ``` `ExecutableMode` is `INVOKE` (callable) vs `INTROSPECT` (discoverable only). > Note: In Spring Framework 6.2+, several fine-grained categories were consolidated/deprecated in favor of `MemberCategory.INVOKE` and `MemberCategory.ACCESS` semantics as GraalVM's model evolved. Know both the classic names and that the model is simplifying. ## JNI shares the type `hints.jni()` also returns a `ReflectionHints` — the *same class*, but a separate instance whose entries are written to `jni-config.json` instead of `reflect-config.json`. So the API for declaring JNI-accessible members is identical; only the target file differs. ## When you need it - DTOs bound by Jackson/Spring MVC (Spring usually auto-registers `@RestController` payloads, but not always for generics or custom (de)serializers) - JPA entities and embeddables - Classes loaded via `Class.forName` in your own code - Anything a library reflects on that lacks its own reachability metadata ## Gotchas - Registering the type but forgetting `INVOKE_DECLARED_CONSTRUCTORS` gives you a visible class you can't instantiate — a very common bug. - Over-registering (e.g. everything with all categories) works but inflates image size and undermines the security/size benefits of closed-world analysis. - Reflection on *generic* type parameters needs the concrete argument types registered too.
- You registered the type but instantiation still fails at runtime — why?You likely omitted a constructor category. A bare registerType(Class) makes the type visible (Class.forName works) but doesn't permit reflective construction. Add MemberCategory.INVOKE_DECLARED_CONSTRUCTORS (or register the specific constructor with ExecutableMode.INVOKE).
- What's the difference between an INVOKE and an INTROSPECT member category?INTROSPECT makes members discoverable via getMethods/getFields but not callable; INVOKE additionally allows actually invoking them. Introspection-only is cheaper and enough when a library only reads metadata (e.g. bean-property discovery) without calling the members.
saying these in an interview costs you the question
- Thinking registerType alone lets you instantiate the class
- Believing reflection hints are never needed because Spring auto-registers everything
- Confusing DECLARED (all members, this class) with PUBLIC (public, including inherited)