skip to content

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%

answer

  1. interface -> JDK, none -> CGLIB
  2. proxyTargetClass=true forces CGLIB
  3. Boot defaults to CGLIB
  4. CGLIB = runtime subclass
  5. final class/method not CGLIB-proxyable

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.

solid answer

~40 s

Spring AOP's default policy: if the target bean implements at least one user interface, it creates a JDK dynamic proxy that implements those interfaces; if it implements none, it falls back to CGLIB, which subclasses the concrete class. You override this by setting proxyTargetClass = true — via @EnableAspectJAutoProxy(proxyTargetClass = true), <aop:config proxy-target-class="true">, or the property spring.aop.proxy-target-class=true — which forces CGLIB even when interfaces exist. Note that Spring Boot sets proxyTargetClass=true by default for its auto-proxying (so Boot apps often get CGLIB regardless). Choose JDK when you cleanly program to interfaces; choose/allow CGLIB when callers need the concrete type or the class has no interface. CGLIB caveats: it can't proxy final classes or final methods, and it invokes the target constructor.

code

java · 12 lines
java
// Default: JDK proxy for interface beans, CGLIB for interface-less beans
@Configuration
@EnableAspectJAutoProxy
class DefaultAop { }

// Force CGLIB (subclass) proxies for ALL AOP beans
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
class CglibEverywhere { }

// Boot property equivalent (application.properties):
// spring.aop.proxy-target-class=false   # restore JDK where interfaces exist

go deeper

for a junior

Know the interface-vs-none default and that a flag can force CGLIB.

for a middle

State the full selection order and the three ways to set proxyTargetClass.

for a senior

Add the Boot default and the CGLIB final/constructor caveats.

for a principal

Reason about codebase-wide consistency and the risks of silent un-advised final methods.

## The two proxy technologies - **JDK dynamic proxy** — `java.lang.reflect.Proxy`, part of the JDK. **Interface-based**: the proxy implements the target's interfaces. Requires the target to implement an interface. - **CGLIB** (bundled inside spring-core) — **subclass-based**: the proxy is a runtime-generated *subclass* of the concrete target class, overriding methods to add advice. Works without any interface. ## Default selection algorithm (Spring AOP) 1. Is `proxyTargetClass` true? -> use **CGLIB**. 2. Else, does the target implement at least one non-internal interface? -> use **JDK dynamic proxy**. 3. Else -> use **CGLIB**. So out of the box: **interface present = JDK proxy; no interface = CGLIB**. ## Overriding the decision Force CGLIB (subclass) proxying: - Java config: `@EnableAspectJAutoProxy(proxyTargetClass = true)` - XML: `<aop:config proxy-target-class="true"/>` or `<aop:aspectj-autoproxy proxy-target-class="true"/>` - Property: `spring.aop.proxy-target-class=true` **Spring Boot note:** Boot's auto-configuration defaults `spring.aop.proxy-target-class` to **true**, so Boot applications typically use **CGLIB by default** even for beans that implement interfaces. Set it to `false` to restore JDK proxying where interfaces exist. ## Trade-offs / caveats **JDK dynamic proxy** - + No extra bytecode library semantics; only interface methods; clean 'program to interfaces'. - - Only methods declared on the interface are advised; concrete-type injection fails. **CGLIB** - + Works with no interface; proxy is assignable to the concrete class, so concrete-type injection works; can advise public methods not on any interface. - - Cannot subclass **final classes** or override **final/private/static** methods (those silently aren't advised). Historically invoked the constructor twice / required objenesis; modern Spring uses Objenesis so the constructor is bypassed for the proxy instance, but you should still not rely on constructor side effects in proxied beans. ## Rule of thumb If you already design around interfaces, JDK proxies are the natural fit. If a bean has no interface, or callers legitimately need the concrete type, CGLIB (or `proxyTargetClass=true`) is appropriate. Consistency across a codebase is often why teams just set `proxyTargetClass=true` everywhere.

  • In a default Spring Boot app, which proxy type do you usually get for a service that implements an interface?
    CGLIB — Boot defaults spring.aop.proxy-target-class to true, so it subclass-proxies even interface-bearing beans unless you turn that off.
  • Name a class shape CGLIB cannot proxy.
    A final class (can't be subclassed) — and it can't override final, private, or static methods, so advice on those is silently skipped.

saying these in an interview costs you the question

  • Saying Spring always uses JDK proxies for Spring AOP.
  • Claiming CGLIB needs an interface.
  • Believing proxyTargetClass only affects performance, not proxy type.

context