HardπŸ‘€ 8-15 years 5 min read Updated Aug 15, 2026

Dynamic Proxy β€” Interview Questions

Reviewed by Gurusankar M. Β· Updated Aug 15, 2026

Asked inAmazonMicrosoftOracleGoogle
#dynamic proxy#invocationhandler#aop#spring#cglib
Report issue

⚑ Short Answer

A dynamic proxy is a class generated at runtime that implements one or more interfaces and routes every method call to a single InvocationHandler.invoke(). That handler can add behaviour β€” logging, timing, transactions, security β€” before/after delegating to the real object. JDK proxies work on interfaces; CGLIB/ByteBuddy subclass concrete classes. It's the mechanism behind Spring AOP, @Transactional, and mock libraries.

β˜•Coffee Chat Question

Concept Made Simple

β€œHow do Java dynamic proxies work, and how do frameworks use them for AOP?”

🧠Mind Map Answer

Remember It Faster

A proxy is a stand-in that looks like the real interface but funnels every call through your invoke() method, so you can wrap cross-cutting behaviour around the real target without editing it.

JDK proxy→Proxy.newProxyInstance — interfaces only
Handler→InvocationHandler.invoke(proxy, method, args)
CGLIB/ByteBuddy→Subclass concrete classes (no interface)
Powers→Spring AOP, @Transactional, mocks

Spring wraps your bean in a proxy: calling save() actually hits the proxy, which opens a transaction, calls the real save(), then commits or rolls back β€” all without a line of transaction code in your method.

Key takeaway: proxies add behaviour around method calls at runtime. JDK = interfaces via InvocationHandler; CGLIB = subclassing β€” and self-invocation inside a bean bypasses the proxy.

Why CGLIB can't proxy a final class or method: CGLIB's whole mechanism is generating a runtime subclass of your target class that overrides each method to route through the interceptor. A final class can't be subclassed at all β€” there's nothing to generate. A final method on a non-final class can't be overridden either, so even a successfully generated subclass leaves that one method un-intercepted; a call to it goes straight to the real implementation and skips the proxy's advice entirely, silently.

If InvocationHandler.invoke() throws, the exception propagates to the caller exactly as if the real method had thrown it directly β€” the proxy transparently rethrows whatever invoke() throws (typically after Method.invoke() unwraps an InvocationTargetException back to its cause). The one sharp edge: if invoke() throws a checked exception that isn't declared in the proxied interface method's throws clause, the JVM wraps it in an unchecked UndeclaredThrowableException instead of letting it through as-is β€” a reminder that the proxy is bound by the interface's declared exception contract, not the real implementation's.

Multiple advices on one method compose as nested proxies, and order matters. A bean with both @Transactional and @Cacheable isn't wrapped by one proxy doing two things β€” Spring builds a chain, one proxy per advice, each wrapping the next like layers of an onion; the outermost proxy's invoke() runs first, calls into the next proxy, and so on until the innermost call finally reaches the real object. If @Cacheable sits outside @Transactional in that chain, a cache hit skips the transaction (and the real method) entirely and returns the cached value straight away β€” completely different behavior than the reverse ordering, where the transaction always opens even on a cache hit. Spring resolves this default ordering by advice type, but @Order (or Ordered) on the underlying Advisor lets you make it explicit rather than relying on the framework's default and being surprised later.

Proxies aren't free, but the overhead is smaller than it sounds. Every proxied call pays for one extra layer of indirection (dispatch through invoke()) plus, for JDK proxies specifically, a reflective Method.invoke() to reach the real implementation β€” reflection carries a real per-call cost relative to a direct method call, though the JIT compiler inlines and optimizes hot reflective call sites aggressively in long-running JVMs, so the gap narrows considerably after warm-up. In practice this overhead is negligible next to what the advice itself typically does (a database transaction, a cache lookup, a network call) β€” it becomes a real concern only in extremely hot, low-latency inner loops, which is exactly the kind of code most teams are careful not to route through Spring-managed beans in the first place.

⌨️Hands-on Keyboard

Learn by Doing

java
interface Repo { void save(String s); }

Repo real = s -> System.out.println("saved " + s);

Repo proxy = (Repo) Proxy.newProxyInstance(
    Repo.class.getClassLoader(),
    new Class<?>[]{ Repo.class },
    (p, method, args) -> {                       // InvocationHandler
        System.out.println("BEGIN tx");
        Object r = method.invoke(real, args);     // delegate
        System.out.println("COMMIT tx");
        return r;
    });

proxy.save("order-1");
Output
BEGIN tx
saved order-1
COMMIT tx

πŸ”₯What If?

Think Beyond the Expected

Why does calling one @Transactional method from another method in the same bean skip the transaction?

Because the proxy only intercepts calls that come through it from outside. An internal `this.otherMethod()` call goes straight to the target object, bypassing the proxy wrapper entirely β€” so the advice (transaction, cache, retry) never runs. This 'self-invocation' gotcha is why Spring's proxy-based AOP can silently skip @Transactional/@Cacheable on internal calls; you fix it by refactoring into a separate bean, self-injecting the proxy, or switching to AspectJ weaving.

πŸ˜‚Real World

Spring's @Transactional, @Cacheable, @Async, @Retryable, and security checks are all proxy-based advice; Mockito generates proxies for mocks; mapping and RPC libraries proxy interfaces to turn method calls into network requests. The self-invocation gotcha is one of the most common 'why didn't my transaction roll back?' bugs in real Spring apps.

🎯Interviewer's Expectation

Keywords they're listening for:

βœ“ runtime-generated proxy over interfacesβœ“ InvocationHandler.invoke intercepts callsβœ“ JDK (interface) vs CGLIB (subclass)βœ“ powers Spring AOP/@Transactionalβœ“ self-invocation bypasses the proxy

⚠️Common Mistakes

  • βœ—Expecting @Transactional to apply on self-invocation
  • βœ—Trying to JDK-proxy a class with no interface
  • βœ—Forgetting CGLIB can't subclass final classes/methods

βœ…Best Practices

  • βœ“Program to interfaces so JDK proxies apply cleanly
  • βœ“Avoid self-invocation for proxied cross-cutting concerns
  • βœ“Use AspectJ weaving when proxy limits get in the way

πŸ”Follow-up Questions

  • 1JDK dynamic proxy vs CGLIB β€” when is each used?
  • 2How does reflection enable dynamic proxies?
  • 3Why can't CGLIB proxy a final class or method?
  • 4How does the JDK dynamic proxy relate to the classic Proxy design pattern (GoF)?
  • 5What happens if InvocationHandler.invoke() throws an exception?

🧩Related Technologies

Spring AOPCGLIBByteBuddyMockito

πŸ“šReferences

Continue Learning with AI

Take this question deeper with your favourite AI assistant. Pick a depth, copy the prompt, or open it directly β€” AI is your learning companion, not a shortcut.

Plain-language foundations

I'm preparing for a software engineering interview and want to understand this from scratch, as a beginner.

Topic: Dynamic Proxy (Advanced Java)
Interview question: "How do Java dynamic proxies work, and how do frameworks use them for AOP?"

Please:
1. Explain the core idea in simple, plain language, using an everyday analogy.
2. Define any technical terms you use.
3. Walk through one small, concrete example.
4. Finish with a single sentence I can easily remember.

Keep the tone friendly and assume I'm new to this topic.

Was this answer helpful?

Support our platform by exploring our recommended products.

As an Amazon affiliate, purchases through these links may earn us a small commission β€” at no extra cost to you. It helps keep Full Stack Interview Guru free.

Related Questions