How Java dynamic proxy works internally
Here’s a practical example of a Java dynamic proxy used to log method calls on a real object — a common pattern in frameworks like Spring AOP.
🎯 Scenario
You have a service:
public interface PaymentService {
void pay(String account, double amount);
}
Implementation:
public class PaymentServiceImpl implements PaymentService {
public void pay(String account, double amount) {
System.out.println("Transferring ₹" + amount + " to " + account);
}
}
Now you want to log each method call without changing the original class.
✅ Proxy + Logging Example
import java.lang.reflect.*;
public class LoggingInvocationHandler implements InvocationHandler {
private Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">> Invoking " + method.getName() + " with " + java.util.Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("<< Done with " + method.getName());
return result;
}
public static void main(String[] args) {
PaymentService realService = new PaymentServiceImpl();
PaymentService proxy = (PaymentService) Proxy.newProxyInstance(
PaymentService.class.getClassLoader(),
new Class[] { PaymentService.class },
new LoggingInvocationHandler(realService)
);
proxy.pay("uday@bank", 5000);
}
}
🧾 Output
>> Invoking pay with [uday@bank, 5000.0]
Transferring ₹5000.0 to uday@bank
<< Done with pay
💡 Real-world Usage
Framework | How it uses dynamic proxy |
---|---|
Spring AOP | Method interception (e.g. @Transactional ) |
JPA/Hibernate | Lazy-loading proxies for entities |
Mockito | Mocks using dynamic proxies |
Java RMI | Remote stubs |
👉 In this dynamic proxy example, Java generates a new class at runtime, and this class:
- Implements the specified interface(s) (
PaymentService
) - Delegates all method calls to your custom
InvocationHandler
🏗 Who generates the class?
The class is created by the JDK Proxy API:
Proxy.newProxyInstance(...)
Under the hood, this dynamically creates a class similar to:
javaCopyEditclass $Proxy123 implements PaymentService {
InvocationHandler handler;
public $Proxy123(InvocationHandler handler) {
this.handler = handler;
}
public void pay(String account, double amount) {
// Delegates to your handler
handler.invoke(this, PaymentService.class.getMethod("pay", ...), new Object[]{account, amount});
}
}
This $Proxy123
class:
- Did not exist in your code
- Is generated and loaded at runtime
- Is stored in memory (not saved as a
.class
file unless debugging is enabled)
🧠 Where is it stored?
- It’s created by
ProxyGenerator
(an internal JDK class) - Loaded by an internal
ClassLoader
- Assigned a synthetic class name like
$Proxy1
,$Proxy2
, etc.