skip to content

Inside registerHints, how do you declare reflection metadata for a type, and what do MemberCategory values control?

level: seniorimportance: should knowfreq 38%

answer

  1. hints.reflection().registerType(Class, categories...)
  2. PUBLIC vs DECLARED (all incl private)
  3. INTROSPECT (read) vs INVOKE (call)
  4. fields/methods/constructors/classes categories
  5. TypeReference.of for off-classpath types

basics

~10 s

Call hints.reflection().registerType(MyClass.class, category...). Each MemberCategory enum value opts a specific kind of member into native metadata — e.g. INVOKE_PUBLIC_METHODS keeps public methods callable via reflection, INVOKE_DECLARED_CONSTRUCTORS keeps all constructors instantiable.

solid answer

~40 s

You use the ReflectionHints sub-registrar: hints.reflection().registerType(Widget.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS). MemberCategory is a fine-grained enum controlling exactly which members are retained and reflectively accessible: it distinguishes PUBLIC vs DECLARED (all, including private) and INTROSPECT (metadata only — you can enumerate them) vs INVOKE (you can actually call/instantiate). So INTROSPECT_PUBLIC_METHODS lets you list public methods; INVOKE_PUBLIC_METHODS lets you call them. There are categories for fields (PUBLIC_FIELDS, DECLARED_FIELDS), constructors, methods, and nested classes (PUBLIC_CLASSES, DECLARED_CLASSES). Registering only what you actually reflect on keeps the binary lean. For types not on the build classpath you register a TypeReference by name instead of a Class literal. You can also register individual fields/methods rather than whole categories when you need surgical hints.

code

java · 29 lines
java
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;

public class ReflectionExampleHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader cl) {
        // Whole-category: instantiate + call all public methods.
        hints.reflection().registerType(Widget.class,
                MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
                MemberCategory.INVOKE_PUBLIC_METHODS,
                MemberCategory.DECLARED_FIELDS);

        // A type not necessarily on the build classpath, by name.
        hints.reflection().registerType(TypeReference.of("com.example.Internal"),
                MemberCategory.INVOKE_PUBLIC_METHODS);

        // Surgical: only this one method, only to invoke it.
        try {
            hints.reflection().registerMethod(
                    Widget.class.getDeclaredMethod("activate"),
                    ExecutableMode.INVOKE);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }
}

go deeper

for a junior

Should know reflection hints come from hints.reflection().registerType.

for a middle

Should name a few MemberCategory values and their meaning.

for a senior

Should explain the public/declared and introspect/invoke axes and use TypeReference for off-classpath types.

for a principal

Should weigh binary-size/metadata cost of over-registration and prefer surgical registerMethod/registerField.

**`ReflectionHints` — the reflection sub-registrar.** `hints.reflection()` returns a `ReflectionHints` you configure per type: ```java hints.reflection().registerType(Widget.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS); ``` This records that, in the native image, `Widget` must be reflectively usable in those ways. **`MemberCategory` — the granularity knob.** It's an enum in `org.springframework.aot.hint`. Two orthogonal axes: 1. **Scope:** `PUBLIC_*` (only public members) vs `DECLARED_*` (every member declared on the class, including private/protected). 2. **Capability:** `INTROSPECT_*` (metadata is retained — you can *enumerate/read* the member's existence and signature via reflection) vs `INVOKE_*` (the member can actually be *invoked*: method called, constructor instantiated). Common values: - Constructors: `INTROSPECT_PUBLIC_CONSTRUCTORS`, `INTROSPECT_DECLARED_CONSTRUCTORS`, `INVOKE_PUBLIC_CONSTRUCTORS`, `INVOKE_DECLARED_CONSTRUCTORS`. - Methods: `INTROSPECT_PUBLIC_METHODS`, `INTROSPECT_DECLARED_METHODS`, `INVOKE_PUBLIC_METHODS`, `INVOKE_DECLARED_METHODS`. - Fields: `PUBLIC_FIELDS`, `DECLARED_FIELDS` (retaining fields for reflective get/set). - Nested classes: `PUBLIC_CLASSES`, `DECLARED_CLASSES`. So if you only call `Class.getMethods()` to inspect signatures, `INTROSPECT_PUBLIC_METHODS` suffices; if you actually `method.invoke(...)`, you need `INVOKE_PUBLIC_METHODS`. **Surgical registration.** Instead of whole categories you can register a specific member: ```java hints.reflection().registerMethod( Widget.class.getDeclaredMethod("activate"), ExecutableMode.INVOKE); hints.reflection().registerField( Widget.class.getDeclaredField("id")); ``` `ExecutableMode.INVOKE` vs `ExecutableMode.INTROSPECT` mirrors the invoke/introspect distinction at the single-member level. This keeps metadata minimal. **Types not on the classpath.** When the class isn't available at build config time (or you're contributing hints for another module's internal type), use `TypeReference.of("com.example.Internal")`: ```java hints.reflection().registerType(TypeReference.of("com.example.Internal"), MemberCategory.INVOKE_PUBLIC_METHODS); ``` **Why the granularity matters.** Every retained member adds to binary size and reflection metadata. Over-registering (`DECLARED_*` everywhere) bloats the image and can mask real design issues; under-registering causes runtime `NoSuchMethodException`/`NoSuchFieldException`. Aim to register exactly the members your reflective code touches. **Gotchas.** - INTROSPECT is *not enough* to call — a classic bug is registering introspection but then invoking, which fails natively. - Registering a type without any category still makes the type *reachable* (loadable by name) but exposes no members. - Newer Spring/GraalVM versions consolidate some categories; check your version's `MemberCategory` for deprecations, but the invoke/introspect and public/declared distinctions are stable concepts.

  • What's the difference between INTROSPECT_PUBLIC_METHODS and INVOKE_PUBLIC_METHODS?
    INTROSPECT retains the method's metadata so you can enumerate/read it via reflection (getMethods), but you cannot call it; INVOKE additionally makes it callable via Method.invoke. Registering only INTROSPECT and then invoking fails natively.
  • How do you register a type that isn't on the build classpath at hint-writing time?
    Use TypeReference.of("fully.qualified.Name") instead of a Class literal in registerType, so you don't need a compile-time dependency on the class.

saying these in an interview costs you the question

  • Thinking INTROSPECT is enough to actually invoke members
  • Believing PUBLIC_* also covers private members
  • Registering DECLARED_* for everything as a default (bloat)
  • Assuming registerType with no category exposes all members

context