Understanding Proxy Pattern in Java: Static, JDK Dynamic, and CGLIB Proxies

The proxy pattern provides a surrogate or placeholder for another object to control access to it. It is useful when a client object cannot or should not directly reference the target object, and the proxy acts as an intermediary. The core idea is to add additional behavior around the target's logic without modifying the target class itself. For example, instead of a person studying law to defend themselves in court (modifying the original object), they hire a lawyer (the proxy) to handle the legal work.

In Java, proxies are typically classified into three categories: static proxy, dynamic proxy (JDK proxy), and CGLIB proxy.

1. Static Proxy

In a static proxy, both the target and the proxy implement the same interface (or extend the same parent class). The proxy holds a reference to the target and adds enhancements before or after calling the target's methods.

Example: An ordering service.

// Interface
public interface IOrderService {
    void processOrder();
}

// Target implementation
public class OrderService implements IOrderService {
    @Override
    public void processOrder() {
        System.out.println("Customer wants to place an order.");
    }
}

// Proxy class (static)
public class OrderServiceProxy implements IOrderService {
    private IOrderService target;

    public OrderServiceProxy(IOrderService target) {
        this.target = target;
    }

    @Override
    public void processOrder() {
        System.out.println("[Proxy] Starting order processing...");
        target.processOrder();
        System.out.println("[Proxy] Order processing finished.");
    }
}

// Usage
public class Application {
    public static void main(String[] args) {
        IOrderService realService = new OrderService();
        IOrderService proxy = new OrderServiceProxy(realService);
        proxy.processOrder(); // The proxy handles extra logic
    }
}

The proxy must implement the same interface as the target, enforcing method signatures. If the proxy does not implement the interface, it still works but loses the compile-time guarantee of matching method names. For multiple interfaces, the proxy can implement all of them separately, but this leads to maintenance overhead when interfaces change.

2. Dynamic Proxy (JDK Proxy)

JDK dynamic proxy creates a proxy object at runtime using java.lang.reflect.Proxy. The proxy object does not need to explicitly implement interfaces, but the target must implement at least one interface. The proxy is generated by providing a class loader, the interfaces to implement, and an InvocationHandler that defines the behavior for all method calls.

Key parts of the Proxy.newProxyInstance method:

  • ClassLoader loader: Typically obtained from the target's class.
  • Class<?>[] interfaces: The interfaces the proxy should implement (must be a subset of the target's interfaces).
  • InvocationHandler h: Handles the method invocation; the invoke() method is where enhancements (e.g., logging, transaction management) are added.

Example with two interfaces:

// Interfaces
public interface IOrderService {
    void processOrder();
}

public interface ILoggingService {
    void log(String message);
}

// Target implementing both
public class OrderAndLogService implements IOrderService, ILoggingService {
    @Override
    public void processOrder() {
        System.out.println("Processing order...");
    }

    @Override
    public void log(String message) {
        System.out.println("Log: " + message);
    }
}

// Proxy factory
public class DynamicProxyFactory {
    private Object target;

    public DynamicProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("[Dynamic Proxy] Before method: " + method.getName());
                    Object result = method.invoke(target, args);
                    System.out.println("[Dynamic Proxy] After method: " + method.getName());
                    return result;
                }
        );
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        OrderAndLogService service = new OrderAndLogService();
        IOrderService orderProxy = (IOrderService) new DynamicProxyFactory(service).getProxy();
        orderProxy.processOrder();

        ILoggingService logProxy = (ILoggingService) new DynamicProxyFactory(service).getProxy();
        logProxy.log("Test message");
    }
}

The dynamic proxy returns an object that conforms to the requested interface. All method on the proxy go through the InvocationHandler, allowing flexible cross-cutting concerns. Drawback: the target must implement at least one interface; interfaces cannot be proxied if none exist.

3. CGLIB Proxy

CGLIB (Code Generation Libray) creates proxies by subclassing the target class at runtime. It does not require the target to implement any interface. Instead, it generates a subclass of the target and overrides its methods. This works with non-final classes (final classes cannot be subclassed). CGLIB uses the Enhancer class and a MethodInterceptor callback to intercept method calls.

// Target class (no interface required)
public class PaymentService {
    public void pay(double amount) {
        System.out.println("Paying $" + amount);
    }
}

// CGLIB proxy interceptor
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxyInterceptor implements MethodInterceptor {
    private Object target;

    public Object createProxy(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("[CGLIB] Before method: " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // call the original method
        System.out.println("[CGLIB] After method: " + method.getName());
        return result;
    }
}

// Usage
public class TestCglib {
    public static void main(String[] args) {
        PaymentService realService = new PaymentService();
        PaymentService proxy = (PaymentService) new CglibProxyInterceptor().createProxy(realService);
        proxy.pay(150.0);
    }
}

CGLIB is often used when the target does not implement an interface (e.g., some legacy classes or third‑party libraries). It is the foundation of proxy‑based AOP in Spring if no intreface is available. Note that because it uses inheritance, methods marked final cannot be intercepted.

Each proxy type has its own use case: static proxies offer compile‑time safety but are cumbersome for many targets; JDK dynamic proxies are lightweight and interface‑based; CGLIB proxies work without interfaces but require the target class to be non‑final and typically need an additional library dependency.

Tags: static-proxy dynamic-proxy cglib-proxy java-design-patterns jdk-proxy

Posted on Mon, 11 May 2026 06:42:35 +0000 by mikeym