Introduction
In our journey through Java's Reflection API, we've learned to inspect classes, create objects, invoke methods, and read metadata via annotations. Each step has taken us deeper into the dynamic capabilities of the JVM. Now, we arrive at one of the most powerful and practical applications of reflection: dynamic proxies. This technique is the secret sauce behind many advanced frameworks, including Spring AOP and mocking libraries like Mockito.
This lesson addresses a key aspect of Java's runtime dynamism: how it can generate and use new types on the fly. We will explore how the java.lang.reflect.Proxy class works, focusing specifically on its mechanism for generating a new class at runtime that implements a set of interfaces, intercepting every method call for custom processing. Understanding this is crucial for grasping how modern backend frameworks achieve cross-cutting concerns like logging, security, and transaction management.
The Proxy Pattern and Its Dynamic Evolution
At its core, a proxy is an object that stands in for another object. In the classic Proxy Design Pattern, you would manually write a proxy class that implements the same interface as a "real" subject. This proxy would hold a reference to the real object and delegate calls to it, perhaps adding some extra logic before or after the delegation.
The limitation of this static approach is its inflexibility. For every class you want to proxy, you must write another class. What if you want to apply the same logic (e.g., logging every method call) to dozens of different service interfaces? This is where dynamic proxies come in. Instead of you writing the proxy class, the Java runtime generates it for you.
Dynamic Proxies in Java | Baeldung
This article from Baeldung provides a concise introduction to dynamic proxies.
Please read the introduction, which explains the core idea of a dynamic proxy as a facade that can pretend to be any interface, routing all method calls to a single handler. Then, read the section on the Invocation Handler. This introduces the central component that defines the proxy's behavior.
As the article explains, the key is that all method invocations on the proxy object are redirected to a single method in an InvocationHandler. This gives you a single point of control to intercept and process calls to any method of any interface you choose to proxy.
The Two Core Components: Proxy and InvocationHandler
Creating a dynamic proxy involves two main parts from the java.lang.reflect package:
-
InvocationHandlerInterface: This is a functional interface that you implement. Its single method,invoke(Object proxy, Method method, Object[] args), is the heart of the proxy's logic. Every time a method is called on the proxy instance, thisinvokemethod is executed instead.proxy: The proxy instance that the method was invoked on.method: TheMethodobject corresponding to the interface method that was called.args: An array of arguments passed to the method.
-
ProxyClass: This class provides the static factory method,newProxyInstance(), which is used to generate the proxy object.
Let's walk through the creation process.
How to Use Java's Reflection API for Dynamic Proxy Classes
This article from W3Computing gives a clear, step-by-step example of creating a simple dynamic proxy.
Please read section 3, "Creating a Simple Dynamic Proxy". Follow the four steps: defining an interface, creating a real implementation, implementing an InvocationHandler, and finally, creating the proxy instance. Pay close attention to the Proxy.newProxyInstance() call and its three arguments.
The call to Proxy.newProxyInstance(classLoader, interfaces, handler) is the critical step. Let's break down its arguments:
ClassLoader classLoader: The class loader that will define the newly generated proxy class. You can usually get this from the target interface's class (Service.class.getClassLoader()).Class<?>[] interfaces: An array ofClassobjects representing the interfaces that the generated proxy class must implement. This is a fundamental constraint: JDK dynamic proxies can only implement interfaces, not extend classes.InvocationHandler handler: An instance of your invocation handler, which will process all method calls made on the proxy.
When proxyService.perform() is called in the example, the JVM doesn't call RealService.perform() directly. Instead, it calls the invoke method on your ServiceInvocationHandler, passing it the Method object for perform(). Your handler then uses reflection (method.invoke(realService, args)) to delegate the call to the actual object, wrapping it with logging statements.
How It Works: Generating a Class at Runtime
You've now seen what dynamic proxies do, but the core of your learning goal is to understand how they do it. The term "dynamic" is literal: Proxy.newProxyInstance() orchestrates the creation of a brand new Java class on the fly.

Let's trace the steps shown in this diagram, which reveal the "inner workings" you're interested in:
- When you call
Proxy.newProxyInstance(), it first callsgetProxyClass(). This method checks a cache to see if a proxy class for the requested set of interfaces has already been created. - If not found in the cache, the process continues. A
ProxyBuilderandProxyGeneratorare employed to generate the raw bytecode for a new class in memory. - This generated class will have a name like
$Proxy0,$Proxy1, etc. It is hardcoded to extendjava.lang.reflect.Proxyand will be declared to implement all the interfaces you provided (e.g.,public final class $Proxy0 extends Proxy implements com.example.MyInterface). - For each method in the interfaces, the
ProxyGeneratorcreates a corresponding method in the new class. The body of each generated method is essentially boilerplate that forwards the call to theinvoke()method of theInvocationHandlerassociated with that proxy instance. - Once the bytecode (
byte[]) is generated, it's passed todefineClass()(a low-level method available throughJavaLangAccessto the core JDK, or available onClassLoaderinstances). This step formally loads the bytecode into the JVM, turning it into a usableClassobject. - This new
Classobject is then cached for future use. - Finally,
newProxyInstance()uses reflection to find the new class's constructor (which takes anInvocationHandleras an argument) and instantiates it to create the final proxy object you get back.
This entire process—bytecode generation, class loading, and instantiation—happens seamlessly at runtime, giving you an object that behaves like an implementation of your interface but with every method call intercepted.
The Limitation of JDK Proxies and the CGLIB Alternative
The mechanism we've just explored has one significant limitation: it is strictly interface-based. The generated class already extends java.lang.reflect.Proxy, and since Java does not support multiple class inheritance, it cannot extend your custom class.
So, what if you need to proxy a class that doesn't implement any interfaces? This is a common scenario, especially with third-party libraries or legacy code. The solution lies in a different proxying technique provided by a library called CGLIB (Code Generation Library). Spring, Hibernate, and many other frameworks use CGLIB as a fallback when JDK proxies aren't sufficient.

The key difference is:
- JDK Proxy: Creates a class that implements one or more interfaces.
- CGLIB Proxy: Creates a class that extends a target class.
CGLIB's approach is to generate a subclass of your target class at runtime. It then overrides the non-final methods to introduce the proxying logic (the "Extra Logic" in the diagram) before (optionally) calling the original method in the superclass (super.method()). This is why CGLIB cannot proxy final classes or final methods.
The following video from the Spring team provides an excellent demonstration of JDK proxies and then discusses the distinction with CGLIB in a real-world context.
This Spring Tips video explains proxies and clearly distinguishes between the JDK's built-in mechanism and CGLIB.
First, watch the demonstration of a JDK dynamic proxy from this segment. It mirrors the examples we've discussed, using Proxy.newProxyInstance and an InvocationHandler to add transactional behavior. Next, skip to the discussion comparing JDK and CGLIB proxies from this part. This directly addresses the "interface vs. concrete class" limitation and explains why Spring uses both strategies.
Conclusion
In this lesson, you've unlocked the mechanics of one of Java's most powerful runtime features. You've seen that dynamic proxies are not magic, but a clever use of runtime code generation and reflection to create flexible and powerful software architectures.
Key Takeaways:
- Dynamic proxies generate new classes at runtime that implement specified interfaces. This is done by creating bytecode in memory and loading it into the JVM.
- All method calls on a proxy instance are routed to the
invokemethod of a singleInvocationHandler, providing a centralized point for adding cross-cutting logic. - The factory for this mechanism is
Proxy.newProxyInstance(), which requires a class loader, an array of interfaces, and an invocation handler. - JDK dynamic proxies are interface-based. They cannot proxy concrete classes that don't implement an interface.
- For proxying classes, frameworks like Spring use CGLIB, which generates a subclass at runtime instead of implementing an interface.
In our next lesson, we will put this all together. We will leverage the full power of the Reflection API, including dynamic object creation and annotation processing, to implement a basic dependency injection container. This will serve as a capstone project for this module, demonstrating how these low-level JVM features are used to build sophisticated application frameworks.