skip to content

When and how do you register JDK dynamic proxy hints with ProxyHints?

level: seniorimportance: should knowfreq 42%

answer

  1. proxies() → proxy-config.json
  2. registerJdkProxy(interfaces...) exact ordered set
  3. JDK proxy = interfaces only, not classes
  4. CGLIB handled by AOT, not ProxyHints
  5. Spring auto-registers framework proxies

basics

~10 s

Use hints.proxies().registerJdkProxy(A.class, B.class...), passing the exact ordered set of interfaces the proxy implements. Needed when code creates a JDK Proxy at runtime. It maps to proxy-config.json.

solid answer

~40 s

`ProxyHints` (via `hints.proxies()`) declares **JDK dynamic proxies** — proxies created by `java.lang.reflect.Proxy.newProxyInstance` for a set of interfaces. In a native image these can't be generated at runtime unless the exact interface set is known at build time. You call `registerJdkProxy(Class... interfaces)` or `registerJdkProxy(TypeReference...)`, and the set/order must match what will be requested. This matters for interface-based proxying: `@Transactional`/`@Async` on an interface, Spring Data repository interfaces, `@ConfigurationProperties` interface bindings, and `HttpServiceProxyFactory` HTTP interface clients. Spring auto-registers most of these, but hand-rolled `Proxy.newProxyInstance` calls in your own code need explicit hints. Note this covers *JDK* proxies only — CGLIB *class-based* proxies (Spring's default for concrete `@Configuration`/service classes) are handled differently by AOT (proxy classes are generated at build time), not via `ProxyHints`. Entries serialize to `proxy-config.json`.

code

java · 13 lines
java
import org.springframework.aot.hint.*;
import java.io.Serializable;

public class ProxyHintsExample implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader cl) {
        // A JDK proxy your code builds via Proxy.newProxyInstance(...)
        hints.proxies().registerJdkProxy(PaymentGateway.class);

        // A different interface SET => a separate, distinct proxy entry
        hints.proxies().registerJdkProxy(PaymentGateway.class, Serializable.class);
    }
}

go deeper

for a junior

Know JDK proxies wrap interfaces and native needs them declared; likely won't go deep.

for a middle

Call registerJdkProxy with the interface set and know it maps to proxy-config.json.

for a senior

Distinguish JDK vs CGLIB handling, explain interface-set/order significance, and know Spring auto-registers framework proxies.

for a principal

Advise on proxy strategy across custom + third-party libs and reason about AOT-generated CGLIB subclasses vs runtime hints.

## JDK dynamic proxies vs CGLIB Two proxying mechanisms exist in the Spring world: - **JDK dynamic proxies** — `Proxy.newProxyInstance(cl, interfaces, handler)` — implement a *set of interfaces*. Spring uses these when proxying an interface. - **CGLIB proxies** — subclass a *concrete class* at runtime. Spring's default for `@Configuration` classes and beans without an interface. `ProxyHints` (`RuntimeHints.proxies()`) is only about the **JDK** kind. GraalVM can generate a JDK proxy at runtime *only if* the exact ordered list of interfaces was declared at build time — the proxy class is synthesized from that list. So you must pre-register every distinct interface combination. CGLIB proxies are handled by Spring AOT differently: during build-time AOT, Spring generates the proxy subclasses ahead of time (as real classes) rather than relying on a runtime hint, so you generally don't register those through `ProxyHints`. ## Registering a JDK proxy ```java hints.proxies().registerJdkProxy(PaymentGateway.class); hints.proxies().registerJdkProxy(PaymentGateway.class, Serializable.class); // different set = separate entry hints.proxies().registerJdkProxy(TypeReference.of("com.acme.Async")); ``` Key rule: **the interface set and its order define the proxy**. `{A, B}` and `{B, A}` are different proxy classes; you must register the exact set the runtime will ask for. Spring often adds marker interfaces (e.g. `SpringProxy`, `Advised`, `DecoratingProxy`) to its own AOP proxies, and its auto-registration accounts for those — a reason to let the framework register framework proxies and only hand-register your bespoke ones. ## Where it's needed - Your code calls `Proxy.newProxyInstance(...)` directly (e.g. a custom dynamic client, a mock/stub, a JDK-proxy-based decorator) - Interface-based AOP that isn't auto-detected - Some third-party libraries that build proxies reflectively For typical Spring features — `@Transactional`/`@Async` on interfaces, Spring Data repositories, `@HttpExchange` HTTP interface clients, `@ConfigurationProperties` interface bindings — Spring's AOT processors already contribute the proxy hints. You step in for custom or third-party dynamic proxies. ## Config file Entries are written to GraalVM's `proxy-config.json` (each entry is an ordered interface list). In the consolidated reachability-metadata format they appear under the reflection/proxy section. ## Gotchas - Wrong or missing interface **order** → runtime failure creating the proxy. - Registering a *class* instead of the interfaces it implements — JDK proxies are interface-only; passing a concrete class is a category error (that's CGLIB territory). - Assuming `ProxyHints` covers CGLIB proxies — it does not. - Missing hint fails at runtime with an error about being unable to create the proxy, not at build.

  • Does ProxyHints cover Spring's CGLIB proxies for @Configuration classes?
    No. ProxyHints is only for JDK dynamic (interface) proxies. CGLIB class-based proxies are generated at build time by Spring AOT as concrete subclasses, so they don't rely on a runtime proxy hint. Passing a concrete class to registerJdkProxy is a category error.
  • Why does the interface ordering in registerJdkProxy matter?
    A JDK proxy class is defined by its ordered interface list, so {A,B} and {B,A} are distinct classes. GraalVM can only synthesize the exact registered combination; if the runtime requests a different order/set than was registered, proxy creation fails at runtime.

saying these in an interview costs you the question

  • Passing a concrete class to registerJdkProxy (JDK proxies are interface-only)
  • Believing ProxyHints handles CGLIB/@Configuration proxies
  • Ignoring interface order/set — thinking any subset works

context