Fundamentals 20 min read

How Does Java’s JDK Dynamic Proxy Work Under the Hood? A Deep Dive

This article explains the difference between static and dynamic proxies in Java, walks through JDK dynamic‑proxy implementation steps, analyzes the underlying Proxy class generation process, and compares performance and usage with static proxies and CGLIB.

Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
How Does Java’s JDK Dynamic Proxy Work Under the Hood? A Deep Dive

1. Static Proxy

Static proxy is a class created at compile time that implements the same interface as the target class and adds extra logic before or after delegating to the target.

/**
 * User interface
 */
public interface IUserService {
    /**
     * Query all users
     */
    void queryUserList();
}

/**
 * Target class implementation
 */
public class UserServiceImpl implements IUserService {
    @Override
    public void queryUserList() {
        System.out.println("Query all user information");
    }
}

/**
 * Proxy class implementation
 */
public class UserServiceProxy implements IUserService {
    private UserServiceImpl userService;

    public UserServiceProxy(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void queryUserList() {
        System.out.println("----Authentication----");
        System.out.println("Query all user information");
        System.out.println("----Log recording----");
    }
}

/**
 * Test
 */
public class StaticAgentTest {
    public static void main(String[] args) {
        UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl());
        userServiceProxy.queryUserList();
    }
}

Running the static proxy prints authentication, the original method output, and logging.

2. Dynamic Proxy

Dynamic proxy differs from static proxy in that the proxy class is generated at runtime. In Java there are two implementations: JDK dynamic proxy (interface‑based) and CGLIB dynamic proxy (class‑based).

2.1 JDK Dynamic Proxy

JDK dynamic proxy can only proxy classes that implement interfaces. It uses reflection to generate a proxy class that forwards method calls to an InvocationHandler.

2.1.1 Implementation Steps

Create an interface and its implementation.

Implement InvocationHandler to define custom logic.

Call Proxy.newProxyInstance to obtain the proxy instance.

/**
 * User interface
 */
public interface IUserService {
    void queryUserList();
}

/**
 * Target class implementation
 */
public class UserServiceImpl implements IUserService {
    @Override
    public void queryUserList() {
        System.out.println("Query all user information");
    }
}

/**
 * InvocationHandler implementation
 */
public class UserServiceInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("----Proxy method before----");
        Object result = method.invoke(target, args);
        System.out.println("----Proxy method after----");
        return result;
    }
}

/**
 * Test JDK dynamic proxy
 */
public class JdkProxyTest {
    public static void main(String[] args) {
        IUserService target = new UserServiceImpl();
        IUserService proxyInstance = (IUserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new UserServiceInvocationHandler(target)
        );
        proxyInstance.queryUserList();
    }
}

Running the example prints “----Proxy method before----”, the original method output, and “----Proxy method after----”.

2.1.2 Underlying Mechanism and Proxy Source Analysis

Proxy.newProxyInstance

creates a proxy class that implements the target interfaces and delegates method calls to the supplied InvocationHandler. The method works by:

Validating the interfaces (visibility, uniqueness, and that they are true interfaces).

Choosing a package for the generated class – com.sun.proxy for public interfaces, otherwise the package of the non‑public interface.

Generating a unique class name like $Proxy0, $Proxy1 … using an AtomicLong counter.

Calling ProxyGenerator.generateProxyClass to produce the bytecode.

Defining the class with defineClass0 and constructing an instance via the constructor that takes an InvocationHandler.

The generated class extends java.lang.reflect.Proxy and contains static Method fields for each interface method, a constructor that stores the InvocationHandler, and overridden methods that invoke the handler’s invoke method.

public final class $Proxy0 extends Proxy implements IUserService {
    private static Method m1; // Object.equals
    private static Method m2; // Object.toString
    private static Method m3; // IUserService.queryUserList
    private static Method m4; // IUserService.delete
    private static Method m0; // Object.hashCode

    public $Proxy0(InvocationHandler h) throws Exception {
        super(h);
    }

    public final void queryUserList() throws Throwable {
        try {
            super.h.invoke(this, m3, (Object[]) null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    // other overridden methods (equals, hashCode, toString, delete) omitted for brevity
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.qy.staticAgent.IUserService").getMethod("queryUserList");
            m4 = Class.forName("com.qy.staticAgent.IUserService").getMethod("delete", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        } catch (ClassNotFoundException e) {
            throw new NoClassDefFoundError(e.getMessage());
        }
    }
}

Saving the generated class file can be enabled by setting the system property sun.misc.ProxyGenerator.saveGeneratedFiles (JDK 8) or jdk.proxy.ProxyGenerator.saveGeneratedFiles (JDK 9+).

Summary

Static proxy is simple but requires a separate class for each target, leading to code duplication. JDK dynamic proxy creates proxy classes at runtime, works only for interfaces, and adds a small reflection overhead. For class‑based proxying, CGLIB can be used (previewed for the next article).

Recommended reading:

Jingdong second interview: Java serialization and deserialization

@ControllerAdvice Global Exception Handling Details

openFeign, where is my time?

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaReflectionDesign PatternDynamic ProxyStatic ProxyJDK ProxyProxyGenerator
Xuanwu Backend Tech Stack
Written by

Xuanwu Backend Tech Stack

Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.