skip to content

When does Spring auto-detect binding types for native images, and when must you register them explicitly? What are the alternatives to @RegisterReflectionForBinding?

level: seniorimportance: should knowfreq 22%

answer

  1. Auto: controller @RequestBody/@ResponseBody signatures
  2. Manual: RestClient/WebClient bodies, generics, manual ObjectMapper
  3. Alt: RuntimeHintsRegistrar + @ImportRuntimeHints
  4. Annotation = declarative sugar over RuntimeHints.reflection()
  5. Validate against the real native image

basics

~20 s

Spring's AOT engine auto-detects DTOs it can see statically, like controller @RequestBody/@ResponseBody types. You register manually when Spring can't infer the type — e.g. bodies passed to RestClient/WebClient at runtime or resolved via generics. Alternatives: a RuntimeHintsRegistrar with @ImportRuntimeHints, or hand-written reflect-config.json.

solid answer

~40 s

Spring's AOT processing already contributes binding hints for types it can resolve from the static bean/component model — the classic case is controller handler signatures, where @RequestBody/@ResponseBody DTOs are discovered and registered for you. You need explicit registration when the type isn't statically visible to that analysis: request/response bodies given to RestClient/RestTemplate/WebClient at call time, types carried only through generics or a ParameterizedTypeReference, polymorphic subtypes, or objects you (de)serialize manually with an ObjectMapper. The declarative fix is @RegisterReflectionForBinding. The programmatic alternative is implementing RuntimeHintsRegistrar and wiring it with @ImportRuntimeHints, calling hints.reflection().registerType(...) or the BindingReflectionHintsRegistrar — better for dynamic/conditional sets of types. As a last resort you can hand-author GraalVM reflect-config.json, but that bypasses Spring's model and is brittle.

code

java · 21 lines
java
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;

// Programmatic alternative: register a computed/conditional set of DTOs,
// reusing the same BindingReflectionHintsRegistrar the annotation uses.
class ClientDtoHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        var binding = new BindingReflectionHintsRegistrar();
        for (Class<?> dto : List.of(OrderDto.class, InvoiceDto.class)) {
            binding.registerReflectionHints(hints.reflection(), dto);
        }
    }
}

@Configuration
@ImportRuntimeHints(ClientDtoHints.class)
class NativeHintsConfig {}

go deeper

for a junior

May not know auto-detection exists at all.

for a middle

Should know controller bodies are auto-detected and clients/generics are not.

for a senior

Should enumerate the gap cases and the RuntimeHintsRegistrar alternative.

for a principal

Should weigh declarative vs programmatic vs raw metadata and design a native validation strategy.

**What Spring auto-detects.** Spring Boot/Framework AOT processing walks the application context's bean definitions and known infrastructure to contribute runtime hints automatically. For binding specifically, the most important auto-detection is **web handler signatures**: when a `@Controller`/`@RestController` method takes an `@RequestBody MyDto` or returns a `@ResponseBody`/`ResponseEntity<MyDto>`, Spring MVC/WebFlux's AOT contributions register the reflection hints for those DTOs. Similarly, many framework integrations register hints for their own well-known types. The heuristic: if the type is reachable through the *static* bean/component model that AOT can inspect, you generally get hints for free. **When you must register manually.** The gap is anything whose type AOT *cannot see statically*: - Bodies handed to an HTTP client at runtime: `restClient.post().body(dto)` / `.retrieve().body(SomeDto.class)`, `restTemplate`, `WebClient` — the DTO type isn't in a scanned signature. - Types carried only through generics / `ParameterizedTypeReference<List<Foo>>`, where erasure hides the concrete element type from analysis. - Polymorphic/`@JsonSubTypes` subtypes that are never named in a static signature. - Manual (de)serialization with an injected `ObjectMapper`. - DTOs used purely inside messaging payloads, caches, or other reflective plumbing not modeled as controller I/O. In all these, add `@RegisterReflectionForBinding` for the root type(s). **Alternative 1 — `RuntimeHintsRegistrar` + `@ImportRuntimeHints`.** For programmatic or *conditional* registration, implement the interface and register hints imperatively: ```java class MyHints implements RuntimeHintsRegistrar { public void registerHints(RuntimeHints hints, ClassLoader cl) { new BindingReflectionHintsRegistrar().registerReflectionHints(hints.reflection(), OrderDto.class); } } ``` Then `@ImportRuntimeHints(MyHints.class)` on a config class. This is the right choice when the set of types is computed (e.g. scanned dynamically, driven by config), or when you need to register resource/proxy/serialization hints alongside reflection. `@RegisterReflectionForBinding` is essentially the declarative sugar over the same underlying `RuntimeHints.reflection()` machinery. **Alternative 2 — hand-written GraalVM metadata.** You can drop a `reflect-config.json` under `META-INF/native-image/<group>/<artifact>/`. This is the lowest-level escape hatch, but it bypasses Spring's hint model, isn't refactoring-safe, and duplicates what Spring can generate — use it only for third-party quirks not otherwise addressable. **Alternative 3 — GraalVM reachability metadata repository / tracing agent.** GraalVM ships a metadata repository and a JVM tracing agent that records reflective access at runtime to emit config. Useful for opaque third-party libraries, but for your own DTOs the Spring annotations are cleaner and version-controlled. **Decision guidance.** Prefer `@RegisterReflectionForBinding` for a small, known set of DTOs. Prefer `RuntimeHintsRegistrar` when the set is dynamic/conditional or you're bundling multiple hint kinds. Reserve raw `reflect-config.json`/agent for third-party gaps. Whatever you choose, **validate against the actual native image** (or `-Dspring.aot.enabled=true` plus native integration tests), because missing binding hints typically fail only at native runtime.

  • Why can't Spring infer the DTO type for restClient.retrieve().body(Foo.class)?
    That body type is chosen at call time inside method bodies, not exposed in a scanned bean/handler signature that AOT statically inspects. So AOT never sees Foo as a binding type and you must register it explicitly.
  • When would RuntimeHintsRegistrar be better than the annotation?
    When the set of types is dynamic or conditional (computed at build time), or when you need to register other hint kinds — resources, proxies, serialization — alongside reflection. It's imperative, so you can loop, branch, and reuse BindingReflectionHintsRegistrar.

saying these in an interview costs you the question

  • Believing Spring auto-registers every DTO in the app
  • Thinking hand-written reflect-config.json is the primary/recommended approach
  • Assuming missing hints fail at build time (they usually fail at native runtime)

context