skip to content

Proxy Mechanics: JDK vs CGLIB

How the proxy is actually built: JDK interface proxies, CGLIB subclasses, how Spring chooses between them, the ProxyFactory and Advised internals, and exposing the current proxy. Interviewers use this to explain bugs rather than trivia — advice that vanishes usually traces back here.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

27

Why do internal (self-invocation) method calls in a Spring bean skip @Transactional/@Cacheable advice, and what does setting exposeProxy=true do about it?

level: juniorimportance: must knowfreq 70%

answer

  1. this bypasses the proxy
  2. advice lives on proxy not target
  3. ThreadLocal holds current proxy
  4. cast AopContext.currentProxy()
  5. default off → IllegalStateException

basics

~20 s

Spring advice like @Transactional lives on a proxy that wraps the bean. When one method calls another via this, the call bypasses the proxy, so no advice runs. Setting exposeProxy=true lets a method fetch the proxy and re-route the call so advice still applies.

solid answer

~40 s

Spring adds behaviors like @Transactional, @Cacheable and @Async through a proxy that wraps your bean; the advice only fires when a call goes through that proxy. When method A inside the bean calls method B using `this.B()`, the call goes straight to the raw target object, not the proxy, so B's annotations are silently ignored — the self-invocation problem. Setting `@EnableAspectJAutoProxy(exposeProxy = true)` makes Spring publish the current proxy in a ThreadLocal. You then call `((MyService) AopContext.currentProxy()).B()` so the internal call routes back through the proxy and B's advice runs. It's a targeted escape hatch; self-injection or splitting into a second bean are cleaner, and AspectJ weaving avoids the problem entirely.

code

java · 20 lines
java
@Service
public class OrderService {

    public void placeOrder(Order o) {
        // self-call via `this` would SKIP @Transactional on charge():
        // this.charge(o);            // <-- advice NOT applied

        // route back through the proxy so advice runs:
        ((OrderService) AopContext.currentProxy()).charge(o);
    }

    @Transactional
    public void charge(Order o) {
        // transactional work
    }
}

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)   // publishes proxy to AopContext
class AopConfig {}

go deeper

for a junior

Must know that self-calls skip advice and that this is why an internal @Transactional 'does nothing'.

for a middle

Should know exposeProxy=true + AopContext.currentProxy() as the fix and the cast requirement.

for a senior

Should weigh it against self-injection / separate bean and know the IllegalStateException failure mode.

for a principal

Frames it as coupling to AOP internals and prefers architectural fixes or AspectJ weaving; knows the ThreadLocal mechanics.

## The problem Spring implements cross-cutting concerns — `@Transactional`, `@Cacheable`, `@Async`, custom `@Aspect` advice — with **proxy-based AOP**. When you ask the container for a bean, you don't get your class instance directly; you get a **proxy** that wraps the real **target** object. The proxy is either a **JDK dynamic proxy** (when the bean implements an interface) or a **CGLIB subclass** (when it doesn't). Advice is attached to the proxy: an incoming call hits the proxy first, the proxy runs the advice (open a transaction, check the cache…), then delegates to the target. ``` caller ──▶ [Proxy: advice] ──▶ [Target bean: your code] ``` ## Why self-invocation breaks it Inside the target object, `this` refers to the **raw target**, not the proxy. So when method `outer()` calls `this.inner()` (or just `inner()`), the call never leaves the target object — it goes straight from raw method to raw method. The proxy is completely bypassed, so any advice on `inner()` (its `@Transactional`, `@Cacheable`, etc.) **does not run**. This is the classic *self-invocation* / *internal call* gotcha, and it fails silently — no error, the annotation just does nothing. ## What exposeProxy does `exposeProxy=true` tells Spring's AOP framework to **publish the current proxy into a ThreadLocal** for the duration of a proxied invocation. You enable it on the proxy config: - `@EnableAspectJAutoProxy(exposeProxy = true)` for `@Aspect`/`@EnableAspectJAutoProxy` setups - `@EnableTransactionManagement` and `@EnableCaching` don't have the flag directly; but the underlying `AbstractAdvisorAutoProxyCreator`/`ProxyConfig` does. In practice enabling `@EnableAspectJAutoProxy(exposeProxy = true)` sets it globally for the auto-proxy creator, or you set `exposeProxy` on a `ProxyFactoryBean`/advisor. With the flag on, inside a proxied call you can retrieve the proxy: ```java ((MyService) AopContext.currentProxy()).inner(); ``` Now `inner()` is invoked *through the proxy*, so its advice runs. ## Key APIs - **`org.springframework.aop.framework.AopContext`** — utility with a static `currentProxy()` method backed by a `ThreadLocal<Object>`. - **`AopContext.currentProxy()`** — returns the proxy that is currently executing on this thread. Cast it to your bean's type/interface. ## Gotchas - If `exposeProxy` is **false** (the default), `AopContext.currentProxy()` throws `IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true'.` - It only works **inside** a proxied call chain — calling it from a thread that didn't enter through the proxy (e.g. a new thread you spawned) throws the same exception because the ThreadLocal isn't set there. - It couples your business code to Spring's AOP internals — generally considered a code smell; prefer restructuring. ## When to use As a last-resort escape hatch when you truly must invoke another advised method on the same bean and can't refactor. Better alternatives: extract the advised method into a **separate bean**, use **self-injection** (inject the bean into itself), or switch to **AspectJ load-time/compile-time weaving**, which advises the actual bytecode so `this` calls are also intercepted.

  • What happens if you call AopContext.currentProxy() when exposeProxy is false?
    It throws IllegalStateException with a message like 'Cannot find current proxy: Set exposeProxy property on Advised to true' — the ThreadLocal was never populated.
  • Name two cleaner alternatives to using AopContext for self-invocation.
    Extract the advised method into a separate bean so the call crosses a proxy boundary, or use self-injection (inject the bean into itself and call through that reference). AspectJ weaving avoids the problem entirely.

saying these in an interview costs you the question

  • Thinking @Transactional works on internal this.method() calls by default
  • Believing exposeProxy is on by default
  • Confusing AopContext.currentProxy() with getting the target object (it returns the proxy, not the raw target)

context

open as a page

What is a CGLIB proxy in Spring, and how does it differ from a JDK dynamic proxy?

level: juniorimportance: must knowfreq 70%

basics

~10 s

A CGLIB proxy is a runtime-generated subclass of your target class that overrides its methods to add behavior like transactions. Unlike a JDK dynamic proxy, it doesn't need the class to implement an interface.

open as a page

What is a JDK dynamic proxy, and what does Spring require of a bean before it can create one for it?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A JDK dynamic proxy is a class the JVM builds at runtime that implements the same interface(s) as your bean and forwards calls to it. Spring can only create one if the target implements an interface.

open as a page

What is org.aopalliance.intercept.MethodInterceptor in Spring AOP, and how does its invoke() method work?

level: juniorimportance: must knowfreq 55%

basics

~10 s

MethodInterceptor is Spring's low-level 'around' advice. Its invoke(MethodInvocation) method runs your code, then calls invocation.proceed() to invoke the real target method, and can run more code after — like a wrapper around each call.

open as a page

How does Spring decide between a JDK dynamic proxy and a CGLIB proxy when creating an AOP proxy for a bean?

level: juniorimportance: must knowfreq 72%

basics

~20 s

In plain Spring's default: if the bean implements at least one interface, Spring makes a JDK dynamic proxy; if it implements no interface, Spring uses CGLIB, which builds a subclass at runtime. Spring Boot changes the default to always use CGLIB.

open as a page

Why doesn't Spring AOP advice fire on final, private, or static methods when using CGLIB proxies?

level: middleimportance: must knowfreq 65%

basics

~10 s

CGLIB works by subclassing your class and overriding methods. Java doesn't let a subclass override final, private, or static methods, so those methods can't be intercepted and their advice is silently skipped.

open as a page

You have a bean MyServiceImpl implements MyService, and Spring AOP proxies it. Why does @Autowired MyServiceImpl (by concrete class) fail, and how do you fix it?

level: middleimportance: must knowfreq 65%

basics

~20 s

The JDK proxy implements the interface MyService, not the class MyServiceImpl, so it isn't a MyServiceImpl. Injecting by the concrete class finds no matching bean (or a class-cast failure). Fix it by injecting the interface MyService.

open as a page

What is Spring's ProxyFactory and how do you use it to create an AOP proxy programmatically?

level: juniorimportance: should knowfreq 45%

basics

~20 s

ProxyFactory is a Spring class that builds an AOP proxy in plain code (no Spring container). You give it a target object, add one or more advices, then call getProxy() to get a wrapped object that runs the advice around the target's methods.

open as a page

How do you enable exposeProxy and correctly retrieve/use AopContext.currentProxy() to re-route an internal call through the proxy?

level: middleimportance: should knowfreq 50%

basics

~20 s

Turn on the flag with @EnableAspectJAutoProxy(exposeProxy = true). Inside the bean, cast the exposed proxy to your type and call the target method through it: ((MyService) AopContext.currentProxy()).method(). That routes the call through the proxy so advice runs.

open as a page

What is the Advised interface and what can you do by casting a live Spring AOP proxy to it?

level: middleimportance: should knowfreq 40%

basics

~20 s

Advised is the interface every Spring AOP proxy implements. Cast a proxy to Advised to inspect or change it at runtime: read the list of advisors, add or remove advice, and see the target and proxy settings.

open as a page

How does Spring decide between a JDK dynamic proxy and a CGLIB proxy, and how do you override that decision?

level: middleimportance: should knowfreq 60%

basics

~10 s

By default Spring uses a JDK dynamic proxy if the target implements an interface, and CGLIB (a runtime subclass) if it doesn't. You force CGLIB everywhere with proxyTargetClass = true.

open as a page

Describe Spring's classic advice interfaces MethodBeforeAdvice, AfterReturningAdvice, and ThrowsAdvice — their method signatures and semantics.

level: middleimportance: should knowfreq 45%

basics

~10 s

They are Spring's simpler advice types. MethodBeforeAdvice runs before a method, AfterReturningAdvice runs after it returns successfully, and ThrowsAdvice runs when it throws. They observe the call but can't change the return value.

open as a page

What is Spring Boot's default for spring.aop.proxy-target-class, and why does it matter?

level: middleimportance: should knowfreq 55%

basics

~20 s

Spring Boot defaults spring.aop.proxy-target-class to true, so it uses CGLIB (runtime subclass) proxies for all beans, even those with interfaces. This means you can inject beans by their concrete class. Set the property to false to get JDK interface proxies again.

open as a page

What does proxyTargetClass=true do, and where can you set it?

level: middleimportance: should knowfreq 58%

basics

~20 s

proxyTargetClass=true tells Spring to always use CGLIB (a runtime subclass) instead of a JDK interface proxy, even when the bean implements interfaces. You set it on the AOP-enabling annotation, e.g. @EnableTransactionManagement(proxyTargetClass = true), or as a property.

open as a page

When would you reach for AopContext.currentProxy()/exposeProxy versus self-injection, a separate bean, or AspectJ weaving? What are the trade-offs?

level: seniorimportance: should knowfreq 45%

basics

~20 s

All solve the same self-invocation problem. AopContext is quickest but couples code to Spring AOP. Self-injection is cleaner and equivalent. Extracting a separate bean is the most honest design. AspectJ weaving fixes it at bytecode level so no re-routing is ever needed.

open as a page

Mechanically, how does a CGLIB proxy get created and instantiated, and what happens with the target's constructor?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Spring generates a subclass of the target that overrides methods to call an interceptor chain, then delegates to the real code. Modern Spring instantiates the proxy via Objenesis without calling the target's constructor, avoiding side-effect duplication.

open as a page

What problem does the TargetSource interface solve, and how do singleton, prototype, and pooled TargetSources differ?

level: seniorimportance: should knowfreq 35%

basics

~20 s

TargetSource decides where the real target object comes from for each proxied call. By default it's a fixed singleton, but you can make it return a fresh prototype instance per call, borrow one from a pool, or keep a per-thread instance — all behind the same proxy.

open as a page

Walk through how Proxy.newProxyInstance and InvocationHandler work together to produce a working proxy at runtime.

level: seniorimportance: should knowfreq 45%

basics

~10 s

Proxy.newProxyInstance(classLoader, interfaces, handler) generates a class implementing those interfaces and returns an instance. Every interface method call is dispatched to handler.invoke(proxy, method, args), where you add behavior and usually call method.invoke(target, args) to delegate.

open as a page

What is an Advisor in Spring AOP, and how do PointcutAdvisor, DefaultPointcutAdvisor, and NameMatchMethodPointcut fit together?

level: seniorimportance: should knowfreq 42%

basics

~20 s

An Advisor bundles an advice with the information about where to apply it. A PointcutAdvisor pairs an advice with a Pointcut. DefaultPointcutAdvisor is the standard implementation; NameMatchMethodPointcut is a Pointcut that matches methods by name.

open as a page

Why can injecting a bean by its concrete class type fail under a JDK dynamic proxy, and how do you fix it?

level: seniorimportance: should knowfreq 50%

basics

~20 s

A JDK dynamic proxy only implements the bean's interfaces, so the proxy object is not an instance of the concrete class. Autowiring by the impl class finds no match. Fix: inject by the interface, or force CGLIB (proxyTargetClass=true).

open as a page

How do you control whether Spring uses CGLIB vs JDK proxies, and what are the tradeoffs of Spring Boot forcing CGLIB by default?

level: principalimportance: should knowfreq 35%

basics

~10 s

Set proxyTargetClass=true to force CGLIB or false to prefer JDK proxies (on @EnableAspectJAutoProxy, @EnableTransactionManagement, etc., or via spring.aop.proxy-target-class). Boot defaults to CGLIB so injecting concrete types always works, at the cost of subclassing constraints.

open as a page

What are the architectural consequences and limits of interface-based JDK proxying that you'd weigh when designing Spring components?

level: principalimportance: should knowfreq 30%

basics

~10 s

Only interface-declared methods can be advised or reached; concrete-type dependencies break; you must program to interfaces. If those constraints hurt, switch to CGLIB (proxyTargetClass=true), accepting its own limits (no final classes/methods).

open as a page

How does AspectJProxyFactory differ from ProxyFactory, and when would you reach for it?

level: middleimportance: nice to knowfreq 25%

basics

~10 s

ProxyFactory works with low-level advices and advisors. AspectJProxyFactory adds a convenience: you can hand it whole @Aspect-annotated classes with addAspect(), and it turns their @Before/@Around/etc. methods into the proxy's advisor chain for you.

open as a page

Explain the internal mechanism behind exposeProxy and AopContext.currentProxy() — how the proxy is published, its lifecycle, and the concurrency/threading implications.

level: principalimportance: nice to knowfreq 25%

basics

~20 s

When exposeProxy is true, the AOP proxy stores itself in a static ThreadLocal at the start of each invocation and restores the previous value in a finally block. AopContext.currentProxy() reads that ThreadLocal, so it's valid only on the invoking thread during the call.

open as a page

Walk through what happens internally when a method is called on a Spring AOP proxy, from ProxyFactory config down to the advisor chain and TargetSource.

level: principalimportance: nice to knowfreq 20%

basics

~20 s

A call hits the proxy's invoke handler. It asks the TargetSource for the target, builds the list of matching interceptors for that method from the advisors, then runs them one by one via a MethodInvocation whose proceed() steps through the chain and finally calls the target method by reflection.

open as a page

How does Spring's classic advice API relate to @AspectJ advice, and how are the simpler advice types executed inside the proxy?

level: principalimportance: nice to knowfreq 25%

basics

~10 s

Every advice type ultimately runs as a MethodInterceptor in the proxy's chain. Simpler advices (before/after-returning/throws) are wrapped by adapters into interceptors. @AspectJ advice is a higher-level, annotation-driven layer built on the same Advisor/interceptor plumbing.

open as a page

What are the practical limitations of CGLIB proxying, and how would you decide between JDK and CGLIB proxies across a codebase?

level: principalimportance: nice to knowfreq 34%

basics

~20 s

CGLIB subclasses the target, so it can't proxy final classes or override final/private/static methods, and it instantiates without your constructor (via Objenesis). JDK proxies need interfaces and only advise interface methods. Choose CGLIB for flexibility (Boot's default) or JDK to enforce interface boundaries.

open as a page