How do java.lang.reflect.Proxy and InvocationHandler create a dynamic proxy, and what are the constraints?
answer
- newProxyInstance(loader, interfaces[], handler)
- All calls funnel to invoke(proxy, method, args)
- Delegate via method.invoke(target, args)
- JDK proxies = interfaces only; CGLIB/ByteBuddy subclass classes
- args is null for no-arg; equals/hashCode/toString also routed; UndeclaredThrowableException
basics
~10 sProxy.newProxyInstance generates a class at runtime that implements your interfaces. Every method call on it is routed to one InvocationHandler.invoke(proxy, method, args) method, where you add logic and usually call method.invoke(target, args) to delegate.
solid answer
~50 sjava.lang.reflect.Proxy.newProxyInstance(classLoader, interfaces, handler) builds a proxy class at runtime that implements the listed interfaces and returns an instance. Each interface method call on that instance is dispatched to your single InvocationHandler.invoke(proxy, Method method, Object[] args). Inside invoke you run cross-cutting logic and typically delegate with method.invoke(target, args), returning its result (or wrapping/altering it). The big constraint is that JDK dynamic proxies can only proxy interfaces — you must have an interface type; you cannot proxy a concrete class (libraries like CGLIB or ByteBuddy do that by generating a subclass instead). Other gotchas: equals/hashCode/toString are also routed through invoke (you must handle them sensibly), checked exceptions thrown by invoke must be declared by the proxied method or you get an UndeclaredThrowableException, and args is null for no-arg methods. This is the engine behind Spring AOP's JDK-proxy mode, Mockito-style mocks, and many interceptors.
code
java · 16 linesinterface Service { String fetch(int id); }
Service real = id -> "data-" + id;
Service proxy = (Service) java.lang.reflect.Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class<?>[]{ Service.class },
(p, method, args) -> {
System.out.println("-> " + method.getName());
try {
return method.invoke(real, args); // delegate to target
} catch (java.lang.reflect.InvocationTargetException e) {
throw e.getCause(); // unwrap real exception
}
});
proxy.fetch(7); // prints "-> fetch", returns "data-7"go deeper
Knows that a dynamic proxy is created at runtime and that calls go to an InvocationHandler, even if hazy on the exact API signature.
Can write Proxy.newProxyInstance with an InvocationHandler, delegate via method.invoke, and state the interfaces-only constraint and CGLIB alternative.
Handles the edge cases — null args, equals/hashCode/toString routing, exception wrapping/unwrapping, self-invocation bypass — and explains how Spring chooses JDK vs CGLIB proxies.
Weighs reflection cost vs codegen, when to push interception into proxies vs explicit code, debuggability of synthetic frames, and the design implications of self-invocation for API boundaries.
## Goal A static (hand-written) proxy forces you to write one forwarding method per interface method, and a fresh class per interface. A **dynamic proxy** lets you write the cross-cutting logic **once** and apply it to *any* interface, with the proxy class **generated at runtime**. ## The two pieces **`java.lang.reflect.InvocationHandler`** is a functional interface with one method: ```java Object invoke(Object proxy, Method method, Object[] args) throws Throwable; ``` You put your interception logic here. **`java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)`** generates (or reuses a cached) class that: - implements every interface in `interfaces`, - holds your handler `h`, - and whose every method body is essentially `return h.invoke(this, theMethod, theArgs);` It returns an instance of that generated class, which you cast to one of the interfaces. ## End-to-end ```java interface Service { String fetch(int id); } class RealService implements Service { public String fetch(int id) { return "data-" + id; } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( Service.class.getClassLoader(), new Class<?>[]{ Service.class }, (proxyObj, method, args) -> { long t0 = System.nanoTime(); try { return method.invoke(real, args); // delegate } finally { System.out.println(method.getName() + " took " + (System.nanoTime()-t0) + "ns"); } }); proxy.fetch(7); // prints timing, returns "data-7" ``` Note the handler does **not** know about `Service` specifically — the same handler logic works for any interface, which is the whole point. ## Method dispatch details - `method` is the `java.lang.reflect.Method` being invoked; `args` is the argument array (**`null`**, not an empty array, when the method takes no parameters). - `method.invoke(target, args)` performs the real delegation via reflection. If the real method throws, reflection wraps it in an `InvocationTargetException`; you normally rethrow its `getCause()` so callers see the original exception. - **`equals`, `hashCode`, `toString`** declared on `Object` are *also* routed to `invoke`. If you don't handle them deliberately, proxy identity/printing can behave oddly. A common idiom is to handle these specially in the handler. - **Exception rule:** if your `invoke` throws a *checked* exception that the proxied interface method does **not** declare, the runtime wraps it in `java.lang.reflect.UndeclaredThrowableException`. Unchecked exceptions and declared checked exceptions pass straight through. ## The central constraint: interfaces only JDK dynamic proxies can implement **interfaces only** — every interface in the array must be an interface, and the generated proxy `implements` them. You **cannot** proxy a concrete class with `java.lang.reflect.Proxy`. To proxy a class (no interface available), you need a library that generates a **subclass** at runtime — **CGLIB** or **ByteBuddy** — overriding non-final methods. (This is why Spring uses JDK proxies when a bean has an interface and CGLIB otherwise; and why `final` classes/methods can't be CGLIB-proxied.) ## Other gotchas - **Self-invocation isn't intercepted:** when the real object calls one of its own methods (`this.other()`), that call bypasses the proxy entirely — the proxy only wraps calls that come *through* it. This is the classic reason an inner `@Transactional` call "doesn't start a transaction" in Spring. - **Performance/debuggability:** reflective dispatch is slower than a direct call and adds synthetic frames to stack traces. - **Class caching:** the JDK caches the generated proxy class per (classloader, interface-set), so repeated `newProxyInstance` calls are cheap. ## Where it's used Spring AOP (JDK-proxy mode), declarative `@Transactional`/`@Cacheable`, JAX-RS/Feign-style HTTP clients built from an interface, mocking frameworks, and RMI-style stubs are all dynamic proxies funneling calls through an `InvocationHandler`.
- Why can't you use java.lang.reflect.Proxy to proxy a class that has no interface?JDK dynamic proxies generate a class that *implements* the supplied interfaces; there is no interface to implement for a plain class, and the generated proxy extends Proxy, not your class. To wrap a class you need a subclass-based proxy (CGLIB/ByteBuddy), which overrides non-final methods — hence final classes/methods can't be proxied that way.
- Why does a self-call inside a Spring bean sometimes skip @Transactional?The proxy only intercepts calls that arrive through it. When a method calls another method on the same object via `this`, the call goes directly to the target and bypasses the proxy, so the surrounding advice (transaction, cache, security) never runs.
saying these in an interview costs you the question
- Claiming JDK dynamic proxies can proxy concrete classes (they need an interface; CGLIB subclasses instead)
- Forgetting that args is null (not empty array) for no-arg methods
- Not knowing that equals/hashCode/toString are also routed through invoke
- Letting InvocationTargetException leak instead of unwrapping getCause()
- Assuming self-invocation on the target is intercepted by the proxy