Integrating Reflection with Proxies in Java (java.lang.reflect.Proxy)

Illustration for Integrating Reflection with Proxies in Java (java.lang.reflect.Proxy)
By Last updated:

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

  1. Spring AOP – Uses dynamic proxies to implement cross-cutting concerns like logging and transactions.
  2. Hibernate – Uses proxies for lazy loading of entities.
  3. Security frameworks – Intercept methods to perform authorization checks.
  4. 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

  1. Can dynamic proxies work with classes?
    No, only with interfaces. Use CGLIB or ByteBuddy for classes.

  2. Is performance a concern with proxies?
    Slightly, but acceptable if not in performance-critical paths.

  3. Can proxies intercept constructors?
    No, they only intercept method calls.

  4. What happens if the target method throws an exception?
    The exception is propagated to the caller.

  5. How does Spring handle proxies internally?
    By creating dynamic proxies around beans with aspects applied.

  6. Can proxies handle multiple interfaces?
    Yes, you can pass multiple interfaces to Proxy.newProxyInstance.

  7. Do proxies work with default methods in interfaces?
    Yes, since Java 8, proxies can intercept default methods too.

  8. What are alternatives to java.lang.reflect.Proxy?
    CGLIB, ByteBuddy, and Javassist.

  9. Can I combine reflection and annotations with proxies?
    Yes, as shown in the annotation-based example.

  10. Are proxies thread-safe?
    Proxies themselves are safe, but the target object must be managed properly.