skip to content

Proxy Choice & proxyTargetClass

Spring picks a JDK proxy when interfaces exist and CGLIB otherwise, unless proxyTargetClass forces CGLIB — which Spring Boot does by default. Knowing the default explains a lot of behaviour people otherwise attribute to magic.

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

questions

5

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

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

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

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