One of the biggest misconceptions about proxies in Java is that they are only useful for network communication or design patterns like Proxy Pattern. In reality, the java.lang.reflect.Proxy
API allows developers to create dynamic proxies at runtime, enabling powerful techniques such as AOP (Aspect-Oriented Programming), dependency injection, and method interception.
A common pain point for developers is writing boilerplate code for logging, security checks, or transaction management across multiple classes. Reflection with proxies solves this problem by intercepting method calls at runtime, letting you inject cross-cutting concerns without changing the business logic.
Think of dynamic proxies as a translator standing between two people: the translator listens to the conversation (method calls), interprets it, and can add their own commentary before passing it along.
What is java.lang.reflect.Proxy?
- A class in the
java.lang.reflect
package used to create proxy objects dynamically. - Works with interfaces, not concrete classes.
- Delegates method calls to an InvocationHandler.
Key Components
- Proxy.newProxyInstance() – Creates the proxy.
- InvocationHandler – Intercepts method calls and defines behavior.
Example: Basic Dynamic Proxy
Step 1: Define an Interface
interface Service {
void perform();
}
Step 2: Implement the Interface
class RealService implements Service {
public void perform() {
System.out.println("Performing real service logic...");
}
}
Step 3: Create an InvocationHandler
import java.lang.reflect.*;
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("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
}
Step 4: Create the Proxy
public class ProxyDemo {
public static void main(String[] args) {
Service realService = new RealService();
Service proxyInstance = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
new LoggingHandler(realService)
);
proxyInstance.perform();
}
}
Output:
Before method: perform
Performing real service logic...
After method: perform
Real-World Applications
- Spring AOP – Uses dynamic proxies to implement cross-cutting concerns like logging and transactions.
- Hibernate – Uses proxies for lazy loading of entities.
- Security frameworks – Intercept methods to perform authorization checks.
- Remote Method Invocation (RMI) – Uses proxies for remote service calls.
📌 What's New in Java Versions?
- Java 5 – Improved generics support, proxies worked seamlessly with parameterized interfaces.
- Java 8 – Lambda expressions made it easier to write InvocationHandlers.
- Java 9 – Module system impacted reflective access; proxies remained unchanged.
- Java 11 – No major updates for Proxy API.
- Java 17 – Sealed classes and encapsulation, but Proxy API still valid.
- Java 21 – No significant updates for Proxy API.
Advanced Example: Annotation-Based Proxy
Define a Custom Annotation
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Loggable { }
Use It in a Service
class AnnotatedService implements Service {
@Loggable
public void perform() {
System.out.println("Executing annotated service...");
}
}
Modify the InvocationHandler
class AnnotationHandler implements InvocationHandler {
private final Object target;
public AnnotationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isAnnotationPresent(Loggable.class)) {
System.out.println("Logging enabled for: " + method.getName());
}
return method.invoke(target, args);
}
}
Run the Proxy
public class AnnotationProxyDemo {
public static void main(String[] args) {
Service service = new AnnotatedService();
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
new AnnotationHandler(service)
);
proxy.perform();
}
}
Output:
Logging enabled for: perform
Executing annotated service...
Pitfalls and Best Practices
Pitfalls
- Works only with interfaces (for classes, use libraries like CGLIB).
- Reflection overhead can impact performance.
- Improper use of
invoke()
can cause infinite recursion if proxy calls itself.
Best Practices
- Use proxies for cross-cutting concerns (logging, security, transactions).
- Cache proxies when possible to reduce overhead.
- Prefer frameworks (Spring AOP) instead of reinventing proxies in production.
- Keep
InvocationHandler
logic lightweight.
Summary + Key Takeaways
java.lang.reflect.Proxy
allows runtime creation of proxy objects.- Useful for logging, security, AOP, and lazy loading.
- Works with interfaces only; use CGLIB for classes.
- Common in Spring, Hibernate, and security frameworks.
- Best used for cross-cutting concerns rather than core logic.
FAQ
-
Can dynamic proxies work with classes?
No, only with interfaces. Use CGLIB or ByteBuddy for classes. -
Is performance a concern with proxies?
Slightly, but acceptable if not in performance-critical paths. -
Can proxies intercept constructors?
No, they only intercept method calls. -
What happens if the target method throws an exception?
The exception is propagated to the caller. -
How does Spring handle proxies internally?
By creating dynamic proxies around beans with aspects applied. -
Can proxies handle multiple interfaces?
Yes, you can pass multiple interfaces toProxy.newProxyInstance
. -
Do proxies work with default methods in interfaces?
Yes, since Java 8, proxies can intercept default methods too. -
What are alternatives to
java.lang.reflect.Proxy
?
CGLIB, ByteBuddy, and Javassist. -
Can I combine reflection and annotations with proxies?
Yes, as shown in the annotation-based example. -
Are proxies thread-safe?
Proxies themselves are safe, but the target object must be managed properly.