Dynamic Proxy
Dynamic proxies create objects at runtime that implement one or more interfaces, delegating method calls to an InvocationHandler. They are the basis for Spring AOP, Mockito, and many Java frameworks.
JDK Dynamic Proxy
Only works with interfaces — the proxy implements the given interfaces:
public interface UserService {
User findById(Long id);
void save(User user);
}
public class UserServiceImpl implements UserService {
public User findById(Long id) { return new User(id, "Alice"); }
public void save(User user) { System.out.println("Saved: " + user); }
}
Creating a Proxy
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxyObj, method, args) -> {
System.out.println("Before: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long elapsed = System.currentTimeMillis() - start;
System.out.println("After: " + method.getName() + " took " + elapsed + "ms");
return result;
}
);
User user = proxy.findById(1L); // logs before/after, returns User
InvocationHandler Interface
public class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Calling " + method.getName());
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getCause(); // unwrap target exception
}
}
}
Usage:
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[] { UserService.class },
new LoggingHandler(new UserServiceImpl())
);
Common Use Cases
1. Logging / Auditing
Log every method call with parameters and return values.
2. Transaction Management
Spring’s @Transactional uses proxies to begin/commit/rollback transactions around method calls.
3. Security Checks
Verify permissions before delegating to the target object.
4. Lazy Loading
Hibernate uses proxies to defer database loading until a property is accessed.
5. Mock Objects
Mockito creates proxy/mock objects for testing without real implementations.
Proxy Limitations
| Limitation | Workaround |
|---|---|
| Only interfaces | Use CGLIB/ByteBuddy for class-based proxies |
| Cannot proxy classes | Subclass-based proxy libraries |
| Only public methods of interfaces | Design with interfaces |
Object methods (equals, hashCode) |
Handle in InvocationHandler |
CGLIB — Class-Based Proxies
When you need to proxy a class (not interface), use CGLIB (used by Spring AOP):
// Spring example — proxies UserServiceImpl class directly
@Transactional
public class UserServiceImpl {
public void save(User user) { ... }
}
CGLIB generates a subclass at runtime that overrides non-final methods.
Checking if Object is a Proxy
if (Proxy.isProxyClass(proxy.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(proxy);
System.out.println("Proxy handler: " + handler.getClass());
}
Best Practices
- Design service layers with interfaces to enable JDK dynamic proxies
- Always unwrap
InvocationTargetExceptionto propagate the real cause - Use established libraries (Spring AOP, ByteBuddy) rather than raw proxies in application code
- Understand the difference between JDK proxies (interfaces) and CGLIB proxies (classes)
- Keep
InvocationHandlerlogic focused — one concern per handler