W3docs

Java Dynamic Proxies

Create proxy implementations of interfaces at runtime in Java with java.lang.reflect.Proxy and InvocationHandler.

Java Dynamic Proxies

A dynamic proxy is an object that implements one or more interfaces, but whose every method call is routed — at runtime — through a single handler you write. The JVM synthesises the proxy class on the fly; you never write the implementation. This is the most powerful corner of java.lang.reflect, and it's how AOP, transparent logging, lazy loading, RPC stubs, and mocking libraries all work. This chapter shows how Proxy.newProxyInstance and InvocationHandler fit together, and what they can and can't do.

The two pieces: Proxy and InvocationHandler

A dynamic proxy needs three inputs:

  1. A class loader (where to define the synthesised class).
  2. An array of interfaces the proxy will implement.
  3. An InvocationHandler — the single method that receives every call.
InvocationHandler handler = (proxy, method, args) -> {
  // called for EVERY method invoked on the proxy
  return ...;   // becomes the method's return value
};

MyService svc = (MyService) Proxy.newProxyInstance(
    MyService.class.getClassLoader(),
    new Class<?>[]{ MyService.class },
    handler);

svc is now a real object implementing MyService. Calling svc.doThing(x) doesn't run any doThing body — there is none — it calls handler.invoke(proxy, <Method doThing>, [x]). The handler decides what to do and what to return.

The invoke signature

Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  • proxy — the proxy instance itself (rarely used; beware calling methods on it from inside invoke, that re-enters the handler and can infinite-loop).
  • method — the Method that was called; method.getName(), method.getReturnType(), its annotations, etc. are all available.
  • args — the arguments as Object[] (null if the method takes none); primitives are boxed.
  • return — whatever the caller should receive; must be assignment-compatible with method.getReturnType() or you get a ClassCastException. For a void method, return null.

A frequent pattern is to forward to a real "target" object: method.invoke(target, args) — wrapping that call with logging, timing, transactions, or retry. That's the decorator via proxy idiom, and it's the basis of Spring AOP.

Interfaces only

The single biggest constraint: java.lang.reflect.Proxy proxies interfaces, not classes. You cannot dynamic-proxy a concrete class with this API. If you need to proxy a class, you reach for a bytecode library (CGLIB, ByteBuddy) that generates a subclass instead — which is why frameworks ship those. For interface-based designs, the built-in Proxy is enough and needs no dependency.

The synthesised proxy class:

  • Extends java.lang.reflect.Proxy and implements your interfaces.
  • Has a generated name like $Proxy0.
  • Routes equals, hashCode, and toString (the Object methods) through invoke too — so your handler should be ready to handle them, or delegate sensibly.

A worked example: a logging + timing proxy

The program defines a Repository interface and a real implementation, then wraps the implementation in a dynamic proxy whose handler logs each call, times it, forwards to the real object, and logs the result — adding cross-cutting behaviour without touching the implementation.

java— editable, runs on the server

What to take from the run:

  • repo was usable exactly as a Repositoryrepo.save(...), repo.count(), repo.find(...) all compiled and ran — yet no class named "logging repository" exists in the source. The JVM generated a $Proxy0 class implementing the interface, and every call landed in LoggingHandler.invoke. The proxy is a real Repository (instanceof returned true).
  • Every business method got an automatic enter/exit log and timing with zero changes to InMemoryRepository. That separation — implementation stays oblivious, cross-cutting concern lives in the handler — is the whole point of AOP, and dynamic proxies are how Spring implements @Transactional, @Cacheable, and friends for interface beans.
  • The handler forwarded each call with method.invoke(target, args), which means a find(99) failure came back as InvocationTargetException. The handler unwrapped it with getCause() and rethrew the real NoSuchElementException, so the caller caught the natural exception rather than a reflection wrapper. A proxy that forgets to unwrap leaks InvocationTargetException to callers.
  • Object methods route through invoke too, so the handler special-cased method.getDeclaringClass() == Object.class and forwarded them plainly. Without that guard, toString/equals/hashCode would also be logged (noisy) or, if you constructed strings from the proxy inside invoke, could recurse. Handling the Object methods deliberately is a standard part of writing a proxy handler.
  • Proxy.isProxyClass(repo.getClass()) confirmed the class is JVM-synthesised, and its name $Proxy0 shows it was generated, not written. Because the API takes a Class<?>[] of interfaces, one proxy can implement several at once — which is how a single mock or stub can satisfy multiple contracts simultaneously.

When to use what

  • Interface, no dependency wantedjava.lang.reflect.Proxy. Built in, simple, interface-only.
  • Need to proxy a concrete class → ByteBuddy or CGLIB (subclass-based). Required because Proxy can't.
  • Just need to stub interfaces in tests → a mocking library (Mockito) built on these mechanisms — don't hand-roll.

Dynamic proxies close out the reflection part: from inspecting a Class, to reading and writing fields, invoking methods, building instances, reading annotations, and finally synthesising entire implementations at runtime. Together they're the toolkit that lets frameworks operate generically over types they were never compiled against — used sparingly and behind clean abstractions, they're what makes Java's ecosystem of containers, mappers, and runners possible.

Practice

Practice

You want to wrap a service that is defined by an interface 'PaymentGateway' so that every method call is logged, without modifying the real implementation. You call 'Proxy.newProxyInstance(...)' passing 'new Class<?>[]{ PaymentGateway.class }' and a handler. Inside the handler's 'invoke', what is the standard way to produce the method's actual result?