Dynamic proxies in Java
java.lang.reflect.Proxy and how frameworks add behavior.
greeter.greet("Ana"), and it works. But there's no class anywhere in your code that implements Greeter. Who answered?An object built at runtime
**java.lang.reflect.Proxy creates, at runtime, an object that implements the interfaces you choose. Every call on it goes to your InvocationHandler, which receives the proxy, the Method being called and its arguments**, and decides what happens.
Object invoke(Object proxy,
Method method,
Object[] args)A calculator with no class
What does this print?
import java.lang.reflect.Proxy;
interface Calc { int add(int a, int b); }
void main() {
var c = (Calc) Proxy.newProxyInstance(
Calc.class.getClassLoader(),
new Class<?>[] { Calc.class },
(p, m, args) -> (int) args[0] * 10);
System.out.println(c.add(2, 3));
}520Compile error
Show the answer
There is no Calc implementation at all. The proxy routes add(2, 3) to the handler, which returns the first argument times 10: 20.
Wrapping a real object
Usually the handler forwards to a real object and adds something around the call: logging, timing, a transaction, a security check. Your class never knows.
InvocationHandler h = (p, m, args) -> {
log("-> " + m.getName());
return m.invoke(realRepo, args);
};Interfaces only
JDK proxies work only for interfaces. Passing a class fails at runtime with **IllegalArgumentException ("... is not an interface"). To proxy concrete classes, frameworks generate subclasses** with bytecode libraries like ByteBuddy.
class Repo { }
Proxy.newProxyInstance(loader,
new Class<?>[] { Repo.class }, h);
// IllegalArgumentExceptionThe missing transaction
In Spring, placeAll() calls this.place(), which is @Transactional. No transaction starts. Why?
Think about it, then reveal the answer
The transaction logic lives in the proxy wrapped around the bean. A call through **this goes straight to the real object and bypasses the proxy**. Self-calls never get the proxy's extras.
Proxies everywhere
Spring's @Transactional and @Cacheable work through proxies around your beans. HTTP client libraries like Retrofit turn an annotated interface into a working client: every method call becomes an HTTP request inside an InvocationHandler.
Key takeaways
- JDK proxies work only for interfaces
- Every call goes to InvocationHandler.invoke(proxy, method, args)
- Class-based proxies need bytecode libraries like ByteBuddy
- Self-calls via this bypass the proxy
💡 A proxy is a receptionist: every request goes through them, and they can log it, check badges, or forward it to the real person.
With Retrofit you write only an interface; the library hands back a java.lang.reflect.Proxy whose handler turns each method call into an HTTP request.
Practice questions
What does this print?
import java.lang.reflect.Proxy;
interface Greeter { String greet(String n); }
void main() {
var g = (Greeter) Proxy.newProxyInstance(
Greeter.class.getClassLoader(),
new Class<?>[] { Greeter.class },
(p, m, a) -> m.getName() + ":" + a[0]);
System.out.println(g.greet("Ana"));
}- Compile error
- Hello, Ana
- greet:Ana
- Throws UnsupportedOperationException
Check your answer
greet:Ana. There is no Greeter implementation class at all: the proxy forwards greet("Ana") to the handler, which returns the method name and first argument.
What does this print?
import java.lang.reflect.Proxy;
class Repo { }
void main() {
Object o = Proxy.newProxyInstance(
Repo.class.getClassLoader(),
new Class<?>[] { Repo.class },
(p, m, a) -> null);
System.out.println("created");
}- Compile error
- Throws IllegalArgumentException
- created
- Throws ClassCastException
Check your answer
Throws IllegalArgumentException. Proxy.newProxyInstance only accepts interfaces. Passing a class fails at runtime with IllegalArgumentException ("... is not an interface").