For a JDK dynamic proxy to work in a native image, what must be true at build time, and how does Spring provide it?
answer
- Ordered interface set registered at build time
- RuntimeHints.proxies().registerJdkProxy(...)
- AOT processAot walks bean factory
- markers: SpringProxy, Advised, DecoratingProxy
- gaps → RuntimeHintsRegistrar + @ImportRuntimeHints
basics
~20 sThe exact set of interfaces the proxy implements must be declared at build time (in proxy/reachability metadata). Spring's AOT processing scans the beans and generates these proxy hints automatically so GraalVM can create the proxy.
solid answer
~40 sGraalVM can only create a JDK dynamic proxy if the *ordered set of interfaces* it implements is registered at build time — historically `proxy-config.json`, now unified reachability metadata. Spring 6's AOT engine (`processAot`) inspects the bean factory: for each proxied bean it computes the interface list (business interface + Spring markers like `SpringProxy`, `Advised`, `DecoratingProxy`) and registers it via `RuntimeHints.proxies().registerJdkProxy(...)`. Boot's build plugin runs this and writes the metadata into the image. When AOT can't infer a proxy — e.g. a proxy you build manually or via a library — you supply a `RuntimeHintsRegistrar` (referenced by `@ImportRuntimeHints`) to register the interface set yourself. Miss a hint and you get a runtime failure creating the proxy in the native binary.
code
java · 15 lines// Manual proxy hint for a proxy AOT can't infer
class PaymentRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.proxies().registerJdkProxy(
PaymentApi.class,
org.springframework.aop.SpringProxy.class,
org.springframework.aop.framework.Advised.class,
org.springframework.core.DecoratingProxy.class);
}
}
@Configuration(proxyBeanMethods = false)
@ImportRuntimeHints(PaymentRuntimeHints.class)
class PaymentConfig { }go deeper
Know the interface set must be declared at build time and Spring usually does it automatically.
Name RuntimeHints.proxies(), processAot, and the manual RuntimeHintsRegistrar path.
Explain ordered interface sets, marker interfaces, and which bean kinds Boot's AOT auto-registers.
Reason about where AOT inference fails (dynamic ProxyFactory, third-party beans) and design a hints strategy for the whole app.
**The core rule.** A `java.lang.reflect.Proxy` is manufactured from a specific, ordered list of interfaces. GraalVM's closed world means it must know every such list at build time. If interfaces `{A, B}` are registered but the app asks for `{A, B, C}` at runtime, proxy creation throws — there's no runtime code generation to fall back on. **Where the metadata lives.** Classic GraalVM used a `proxy-config.json` file under `META-INF/native-image/...`. Modern GraalVM (and Spring's model) uses **reachability metadata** and Spring's `RuntimeHints` abstraction, which has a dedicated `ProxyHints` section (`hints.proxies().registerJdkProxy(Class... interfaces)`). **How Spring populates it automatically.** During the build, the Spring Boot plugin's `processAot` goal invokes `spring-context`'s AOT engine. It refreshes the application context in a special AOT mode, walks every bean definition, and for each bean that will be proxied it asks the proxy infrastructure which interfaces the proxy needs. For an AOP/interface proxy that's the target's business interfaces PLUS Spring's internal markers — commonly `org.springframework.aop.SpringProxy`, `org.springframework.aop.framework.Advised`, and `org.springframework.core.DecoratingProxy`. Those exact sets are handed to `RuntimeHints.proxies()`, serialized into the image. **Manual hints for the gaps.** AOT can't see proxies you create dynamically (e.g. `ProxyFactory` used in your own code, MapStruct/feign-style generated clients, some library beans). For those you implement: ```java class MyHints implements RuntimeHintsRegistrar { public void registerHints(RuntimeHints hints, ClassLoader cl) { hints.proxies().registerJdkProxy(MyApi.class, org.springframework.aop.SpringProxy.class); } } ``` and wire it with `@ImportRuntimeHints(MyHints.class)` on a config class (or via `META-INF/spring/aot.factories`). **Edge cases & gotchas.** - **Order matters**: `Proxy` treats the interface list as ordered; the registered set must match what Spring requests. - **Marker interfaces are easy to forget** when hand-registering — omit `SpringProxy`/`Advised` and the runtime set won't match the registered one. - **`@ConfigurationProperties` / `@HttpExchange` interface clients** and `spring-data` repository interfaces are proxied — Boot's AOT contributions register them, but custom equivalents need your hints. - CGLIB class proxies can't be salvaged this way at all — hints register interface sets, not generated subclasses. **When to use.** Rely on automatic hints for standard `@Transactional`/`@Cacheable`/repository/interface-client beans. Reach for a `RuntimeHintsRegistrar` only when a native build fails with a missing-proxy error for a proxy AOT couldn't discover.
- Which Spring marker interfaces typically appear in an AOP proxy's interface set?`org.springframework.aop.SpringProxy`, `org.springframework.aop.framework.Advised`, and `org.springframework.core.DecoratingProxy`, alongside the bean's business interface(s). Hand-registered proxy hints must include these or the runtime set won't match.
- How do you supply a proxy hint Spring's AOT engine couldn't infer?Implement `RuntimeHintsRegistrar`, call `hints.proxies().registerJdkProxy(...)`, and register it via `@ImportRuntimeHints(MyHints.class)` or an `aot.factories` entry.
saying these in an interview costs you the question
- Saying you register the concrete class for a JDK proxy (you register its interfaces)
- Forgetting the Spring marker interfaces when hand-writing proxy hints
- Assuming AOT discovers proxies created by arbitrary runtime code