Java Dynamic Proxies
Create proxy implementations of interfaces at runtime in Java with java.lang.reflect.Proxy and InvocationHandler.
Java Dynamic Proxies
A dynamic proxy is an object that implements one or more interfaces, but whose every method call is routed — at runtime — through a single handler you write. The JVM synthesises the proxy class on the fly; you never write the implementation. This is the most powerful corner of java.lang.reflect, and it's how AOP, transparent logging, lazy loading, RPC stubs, and mocking libraries all work. This chapter shows how Proxy.newProxyInstance and InvocationHandler fit together, and what they can and can't do.
The two pieces: Proxy and InvocationHandler
A dynamic proxy needs three inputs:
- A class loader (where to define the synthesised class).
- An array of interfaces the proxy will implement.
- An
InvocationHandler— the single method that receives every call.
InvocationHandler handler = (proxy, method, args) -> {
// called for EVERY method invoked on the proxy
return ...; // becomes the method's return value
};
MyService svc = (MyService) Proxy.newProxyInstance(
MyService.class.getClassLoader(),
new Class<?>[]{ MyService.class },
handler);svc is now a real object implementing MyService. Calling svc.doThing(x) doesn't run any doThing body — there is none — it calls handler.invoke(proxy, <Method doThing>, [x]). The handler decides what to do and what to return.
The invoke signature
Object invoke(Object proxy, Method method, Object[] args) throws Throwableproxy— the proxy instance itself (rarely used; beware calling methods on it from insideinvoke, that re-enters the handler and can infinite-loop).method— theMethodthat was called;method.getName(),method.getReturnType(), its annotations, etc. are all available.args— the arguments asObject[](nullif the method takes none); primitives are boxed.- return — whatever the caller should receive; must be assignment-compatible with
method.getReturnType()or you get aClassCastException. For avoidmethod, returnnull.
A frequent pattern is to forward to a real "target" object: method.invoke(target, args) — wrapping that call with logging, timing, transactions, or retry. That's the decorator via proxy idiom, and it's the basis of Spring AOP.
Interfaces only
The single biggest constraint: java.lang.reflect.Proxy proxies interfaces, not classes. You cannot dynamic-proxy a concrete class with this API. If you need to proxy a class, you reach for a bytecode library (CGLIB, ByteBuddy) that generates a subclass instead — which is why frameworks ship those. For interface-based designs, the built-in Proxy is enough and needs no dependency.
The synthesised proxy class:
- Extends
java.lang.reflect.Proxyand implements your interfaces. - Has a generated name like
$Proxy0. - Routes
equals,hashCode, andtoString(theObjectmethods) throughinvoketoo — so your handler should be ready to handle them, or delegate sensibly.
A worked example: a logging + timing proxy
The program defines a Repository interface and a real implementation, then wraps the implementation in a dynamic proxy whose handler logs each call, times it, forwards to the real object, and logs the result — adding cross-cutting behaviour without touching the implementation.
What to take from the run:
repowas usable exactly as aRepository—repo.save(...),repo.count(),repo.find(...)all compiled and ran — yet no class named "logging repository" exists in the source. The JVM generated a$Proxy0class implementing the interface, and every call landed inLoggingHandler.invoke. The proxy is a realRepository(instanceofreturnedtrue).- Every business method got an automatic enter/exit log and timing with zero changes to
InMemoryRepository. That separation — implementation stays oblivious, cross-cutting concern lives in the handler — is the whole point of AOP, and dynamic proxies are how Spring implements@Transactional,@Cacheable, and friends for interface beans. - The handler forwarded each call with
method.invoke(target, args), which means afind(99)failure came back asInvocationTargetException. The handler unwrapped it withgetCause()and rethrew the realNoSuchElementException, so the caller caught the natural exception rather than a reflection wrapper. A proxy that forgets to unwrap leaksInvocationTargetExceptionto callers. Objectmethods route throughinvoketoo, so the handler special-casedmethod.getDeclaringClass() == Object.classand forwarded them plainly. Without that guard,toString/equals/hashCodewould also be logged (noisy) or, if you constructed strings from the proxy insideinvoke, could recurse. Handling theObjectmethods deliberately is a standard part of writing a proxy handler.Proxy.isProxyClass(repo.getClass())confirmed the class is JVM-synthesised, and its name$Proxy0shows it was generated, not written. Because the API takes aClass<?>[]of interfaces, one proxy can implement several at once — which is how a single mock or stub can satisfy multiple contracts simultaneously.
When to use what
- Interface, no dependency wanted →
java.lang.reflect.Proxy. Built in, simple, interface-only. - Need to proxy a concrete class → ByteBuddy or CGLIB (subclass-based). Required because
Proxycan't. - Just need to stub interfaces in tests → a mocking library (Mockito) built on these mechanisms — don't hand-roll.
Dynamic proxies close out the reflection part: from inspecting a Class, to reading and writing fields, invoking methods, building instances, reading annotations, and finally synthesising entire implementations at runtime. Together they're the toolkit that lets frameworks operate generically over types they were never compiled against — used sparingly and behind clean abstractions, they're what makes Java's ecosystem of containers, mappers, and runners possible.
Practice
You want to wrap a service that is defined by an interface 'PaymentGateway' so that every method call is logged, without modifying the real implementation. You call 'Proxy.newProxyInstance(...)' passing 'new Class<?>[]{ PaymentGateway.class }' and a handler. Inside the handler's 'invoke', what is the standard way to produce the method's actual result?