Welcome back to our series on Java Proxies! If you’ve been following along, we’ve already discussed the conceptual how and why of the Proxy Pattern, learned how to write a static proxy, introduced the idea behind dynamic proxies, and looked at how some of the Java giants like Spring, Hibernate, and Mockito use dynamic proxies to power features that you and I use every day.
Now, it’s time to roll up our sleeves and look at the first “real” tool in our kit: JDK Dynamic Proxies.
This is the native way to do things. No external libraries, no Maven dependencies, no other shenanigans. These are built right into the JDK in the java.lang.reflect package. They’re elegant, powerful, and a tad bit opinionated. Let’s dive in!
Meet java.lang.reflect.Proxy
In the early days of Java, if you wanted a proxy, you had to write it by hand. If you had 50 interfaces, you wrote 50 proxy classes. It was tedious, error-prone, and let’s face it: a bit boring!
Enter the Reflection API. It gave Java the ability to look into a mirror.
Just like you can inspect your face and your body by looking into a mirror, Reflection API allows a Java program to inspect its own classes, interfaces, fields, and methods at runtime. By using reflection, Java can “ask” an object what methods it has, and then execute them dynamically.
The JDK Dynamic Proxy mechanism, introduced way back in JDK 1.3, allows you to create proxy instances at runtime. Instead of a .class file sitting on your disk for a specific proxy, the JVM generates the bytecode for the proxy class on the fly in memory.
The star of the show is the java.lang.reflect.Proxy class. It provides a static method that is the “Open Sesame” of the proxy world:
Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
Think of this method as a factory. You give it a ClassLoader, a list of interfaces you want the proxy to “pretend” to be, and a “brain” (the InvocationHandler). In return, it hands you an object that implements all those interfaces.
It does need an Interface
Did you read the last paragraph carefully? You give it a ClassLoader, a list of interfaces you want the proxy to “pretend” to be, and a “brain” (the InvocationHandler). In return, it hands you an object that implements all those interfaces.
Yes, JDK Dynamic Proxies have one non-negotiable rule. They only work with interfaces. If you have a concrete class (say, UserService) that doesn’t implement an interface, the JDK Dynamic Proxy cannot help you.
Why the Interface constraint?
It comes down to two things: How JDK Dynamic Proxy creates the proxy class, and how Java handles inheritance.
Java doesn’t support multiple inheritance: You cannot create a class extending more than one parent class. However, a class can implement multiple interfaces at the same time.
The proxy classes created by JDK Dynamic Proxy already extend java.lang.reflect.Proxy. In order to be a drop-in replacement of your real class, they’d also need to extend it: something that Java doesn’t allow. So they do the next best thing. They implement the same interface(s) implemented by your real class.
This is why the newProxyInstance method takes Class<?>[] interfaces as one of its arguments. Your real class can implement multiple interfaces, hence all of those interfaces need to be passed on to the newProxyInstance method in order for it to create the proxy class.
Understanding the InvocationHandler
If the Proxy class is the body of our dynamic object, the InvocationHandler is the brain.
In a standard Java setup, when you call a method on an object, the JVM knows exactly where the method’s code lives. But a dynamic proxy is created at runtime; it doesn’t have its own hardcoded logic. It needs a middleman to decide what happens when a method is triggered. That’s why the InvocationHandler is mandatory. It provides a centralized place to handle every single method call made to the proxy.
It’s interface is simple, just one method:
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable;
Here, proxy is the instance of the proxy itself, method is an object representing the specific interface method being called, and args is an array of objects containing the arguments to be passed to the method.
Because every method call to the proxy funnels through this single point, you have total control. You can define the dynamic proxy’s behavior by providing your own implementation of the InvocationHandler interface, and by doing so, you can inject functionality before or after the actual method call.
Moreover, you can even decide whether you want to call the actual method or not. For example, for dynamic proxies created for Mockito.spy(), the actual method is called only if there isn’t a subbing present for it.
Be careful, though
You might have noticed that invoke() takes an instance of proxy as one of its arguments. It might be tempting for you to use it inside the InvocationHandler – but be very careful.
If you call a method on the proxy instance itself inside the invoke method (for example, proxy.toString()), the JVM will intercept that call too and send it right back to invoke. This creates an infinite loop that ends in the dreaded StackOverflowError. Usually, if you need to call a method, you should call it on the target (the real object), not the proxy.
Hands-on: Building a Generic LoggingHandler
Enough theory, isn’t it? Now let’s build something!
Let’s build a dynamic proxy that logs the execution time and arguments of every method call in your application… without sprinkling log.info() statements everywhere. We’ll create an InvocationHandler implementation that does that, and then use it to create a proxy for a real class containing business logic. Here goes:
First of all, let’s build an InvocationHandler implementation that logs the arguments and execution time:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingHandler implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(LoggingHandler.class);
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info(">>> Entering: {}", method.getName());
log.info(">>> Arguments: {}", (args == null) ? "[]" : Arrays.toString(args));
long start = System.nanoTime();
// This is where we call the ACTUAL method on the ACTUAL object
Object result = method.invoke(target, args);
long end = System.nanoTime();
log.info(">>> Result: {}", result);
log.info(">>> Time taken: {}ns", (end - start));
return result;
}
}
Let’s decode what’s happening in the above piece of code:
- We created a class
LoggingHandlerthatimplements InvocationHandler. - We overrode
invoke(), and inside, we wrapped the actual method call –method.invoke(target, args)with logging statements that specified entry, exit, and execution time of the method. - The
resultreturned by the actual method call was also returned byinvoke().
Notice that we haven’t defined our business logic as yet. The dynamic nature of this proxy means that any suitable Java method can be proxied and passed on to invoke() to log its vitals.
Anyway, let’s now build a simple calculator to show this dynamic proxy in action.
As required by JDK Dynamic Proxy, let’s define an interface first:
public interface Calculator {
int add(int a, int b);
int multiply(int a, int b);
}
And a simple, bare-bones implementation:
public class RealCalculator implements Calculator {
public int add(int a, int b) { return a + b; }
public int multiply(int a, int b) { return a * b; }
}
Now let’s put this all together:
public class Main {
public static void main(String[] args) {
// 1. Create the real object
Calculator realCalc = new RealCalculator();
// 2. Create the logging handler
LoggingHandler handler = new LoggingHandler(realCalc);
// 3. Create the Proxy
Calculator proxyCalc = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class[] { Calculator.class },
handler
);
// 4. Use the proxy!
int sum = proxyCalc.add(5, 10);
int product = proxyCalc.multiply(3, 4);
}
}
If you execute the Main class now, you will see logging statements detailing the entry, exit, and time taken for both add() and multiply() methods.
Notice how we didn’t add any logging statements to the real add() and multiply() methods – the proxy took care of it! When you called proxyCalc.add(5, 10), the JVM didn’t go to RealCalculator. It went to LoggingHandler. The handler printed the logs, then used Reflection (method.invoke) to call the real add method on realCalc.
Performance Considerations
A common question is: “Is this slow?”
In the early days of Java, Reflection was indeed slow. However, modern JVMs are incredibly good at optimizing Dynamic Proxies. After a few calls, the JIT (Just-In-Time) compiler can often inline these calls, making the overhead negligible for most business applications.
That said, if you are calling a method millions of times per second in a tight loop, you might want to measure the impact. But for 99% of use cases (Spring @Transactional, Hibernate Lazy Loading, etc.), it’s plenty fast!
Wrapping Up
The JDK Dynamic Proxy is a beautiful example of the “Open-Closed Principle” in action. You can add behavior (logging, security, caching) to any interface without changing a single line of the original code.
Key Takeaways:
- Built-in: No extra libraries needed.
- Interface-based: Your target must implement an interface.
- Centralized: All calls go through one
InvocationHandler.
In the next part of this series, we’ll tackle the limitation we found today: What if we don’t have an interface? That’s where CGLIB and Byte Buddy enter the ring. Stay tuned!
Leave a Reply