skip to content

For a JDK dynamic proxy to work in a native image, what must be true at build time, and how does Spring provide it?

level: middleimportance: should knowfreq 45%

answer

  1. Ordered interface set registered at build time
  2. RuntimeHints.proxies().registerJdkProxy(...)
  3. AOT processAot walks bean factory
  4. markers: SpringProxy, Advised, DecoratingProxy
  5. gaps → RuntimeHintsRegistrar + @ImportRuntimeHints

basics

~20 s

The 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 s

GraalVM 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
java
// 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

for a junior

Know the interface set must be declared at build time and Spring usually does it automatically.

for a middle

Name RuntimeHints.proxies(), processAot, and the manual RuntimeHintsRegistrar path.

for a senior

Explain ordered interface sets, marker interfaces, and which bean kinds Boot's AOT auto-registers.

for a principal

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

context