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 InvocationTargetException to 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 InvocationHandler logic focused — one concern per handler