skip to content

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%

answer

  1. interfaces -> JDK, none -> CGLIB
  2. CGLIB = runtime subclass
  3. proxyTargetClass=true forces CGLIB
  4. Boot default spring.aop.proxy-target-class=true
  5. DefaultAopProxyFactory decides

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.

solid answer

~40 s

Spring wraps a bean in a proxy when it needs AOP (transactions, caching, custom aspects). The default rule in the core framework: if the target class implements at least one interface, Spring creates a JDK dynamic proxy that implements those interfaces; if it implements none, Spring falls back to CGLIB, which generates a runtime subclass of the target. Two overrides change this. Setting proxyTargetClass=true forces CGLIB even when interfaces exist. And Spring Boot's auto-configuration sets spring.aop.proxy-target-class=true by default, so a Boot app uses CGLIB everywhere unless you opt out. So the honest interview answer is: 'plain Spring picks JDK-when-interfaces / CGLIB-otherwise, but Spring Boot defaults to CGLIB for everything.'

code

java · 16 lines
java
// Plain Spring: this bean implements an interface -> JDK dynamic proxy by default
public interface OrderService { void place(); }

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void place() { /* ... */ }
}

// Force CGLIB even though an interface exists:
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
class TxConfig { }

// In Spring Boot this is already the effective default because
// application.properties carries: spring.aop.proxy-target-class=true

go deeper

for a junior

Know the one-liner: interface -> JDK proxy, no interface -> CGLIB; Boot defaults to CGLIB.

for a middle

Explain proxyTargetClass=true and the spring.aop.proxy-target-class Boot default, and how to flip it.

for a senior

Connect proxy choice to injection-by-class vs injection-by-interface failures and mockability.

for a principal

Reason about defaults across teams: why Boot standardized on CGLIB, and when to deliberately choose JDK proxies for boundary hygiene.

## What an AOP proxy is Spring implements cross-cutting concerns (declarative transactions via `@Transactional`, caching via `@Cacheable`, and any custom `@Aspect`) by wrapping your bean in a **proxy** object. Callers get the proxy instead of the raw bean; the proxy runs the advice (extra behavior) before/after delegating to your real object. Spring has two proxy technologies: - **JDK dynamic proxy** — built into the JDK (`java.lang.reflect.Proxy`). It can only proxy **interfaces**: it generates a class that implements the target's interface(s) and forwards calls through an `InvocationHandler`. The proxy is *not* a subclass of your concrete class — it only shares the interface type. - **CGLIB proxy** — Spring generates a **subclass** of your concrete class at runtime (CGLIB is repackaged inside `spring-core` as `org.springframework.cglib`) and overrides methods to insert advice. It needs a concrete, non-`final` class. ## The default selection rule (core Spring) Inside `DefaultAopProxyFactory` (used by `ProxyFactory`/`ProxyCreatorSupport`), the logic is roughly: if `proxyTargetClass` is true, OR the target has no interfaces, OR the target is already a proxy class, use CGLIB; **otherwise use a JDK dynamic proxy**. Practically: - Bean implements one or more interfaces -> **JDK dynamic proxy** (default). - Bean implements no interface -> **CGLIB** (no choice — JDK proxies can't proxy a bare class). ## Override 1 — `proxyTargetClass=true` Available on `@EnableAspectJAutoProxy(proxyTargetClass = true)`, `@EnableTransactionManagement(proxyTargetClass = true)`, `@EnableCaching(proxyTargetClass = true)`, and on `ProxyFactoryBean`/`ProxyFactory`. When true, Spring **forces CGLIB** even if interfaces exist — so the proxy is assignable to the concrete class type. ## Override 2 — Spring Boot's default Spring Boot's `AopAutoConfiguration` sets the property **`spring.aop.proxy-target-class=true` by default** (since Boot 2.0). So in a normal Boot application, CGLIB is used for everything, even beans that implement interfaces. To restore the JDK-when-interfaces behavior you must explicitly set `spring.aop.proxy-target-class=false`. ## Why the distinction matters - With a **JDK proxy**, the proxy is only type-compatible with the **interface**, not the class. Injecting the bean by its concrete class (`@Autowired MyServiceImpl x`) fails; you must inject the interface. - With **CGLIB**, the proxy IS-A subclass, so both interface and class injection work — one reason Boot prefers it. ## Gotchas - Neither proxy intercepts **self-invocation** (a method calling `this.other()` inside the same class bypasses the proxy) — that's orthogonal to proxy type. - CGLIB can't override `final` classes/methods or `private`/`static` methods, so advice silently doesn't apply there. ## When to use which Default to Boot's CGLIB unless you have a strong reason (e.g. a legacy `final` class or you specifically want interface-only proxies for clean boundaries and mockability).

  • In a default Spring Boot app, a service implements an interface. Which proxy is used and why?
    CGLIB, because Boot's AopAutoConfiguration defaults spring.aop.proxy-target-class to true, which forces CGLIB regardless of interfaces.
  • Why can't a JDK dynamic proxy be used for a class with no interfaces?
    JDK proxies are generated as classes implementing given interfaces via java.lang.reflect.Proxy; with no interface there is nothing to implement, so Spring must subclass the target with CGLIB.

saying these in an interview costs you the question

  • Claiming Spring always uses CGLIB in plain (non-Boot) Spring
  • Claiming JDK proxies subclass the target class
  • Saying proxyTargetClass=true switches to JDK proxies
  • Not knowing Spring Boot flips the default to CGLIB

context