Spring AOP creates proxies two ways, and the choice changes what your code can do. A JDK dynamic proxy implements the target's interfaces — it's a sibling that delegates to the real object. A CGLIB proxy subclasses the target and overrides its methods. The historical rule was "JDK if the bean has an interface, CGLIB otherwise," but since Spring Boot 2.x, CGLIB is the default even for interfaces (spring.aop.proxy-target-class=true).
JDK dynamic proxy
Built into the JDK via java.lang.reflect.Proxy. It generates a class that implements the target's interfaces and routes every call through an InvocationHandler.
public interface PaymentService { void pay(); }
@Service
class StripePaymentService implements PaymentService {
@Transactional public void pay() { ... }
}
The proxy is a PaymentService, not a StripePaymentService. Consequences:
- You can only inject it by the interface type.
@Autowired StripePaymentServicefails. - Only interface-declared methods are advised; methods not on the interface are invisible.
CGLIB proxy
CGLIB (now bundled inside spring-core) generates a runtime subclass of the target and overrides each method to insert advice.
class StripePaymentService$EnhancerBySpringCGLIB extends StripePaymentService { ... }
Because it's a subclass, the proxy is-a StripePaymentService, so injection by concrete type works. Limitations follow from subclassing:
finalclasses can't be proxied,finalmethods can't be advised (you can't override them).- The target needs an instantiable constructor (modern CGLIB uses Objenesis, so a no-arg constructor isn't strictly required).
- It creates an extra subclass per bean.
How Spring chooses
proxyTargetClass = true(Spring Boot default) → always CGLIB.- Otherwise, target implements interfaces → JDK proxy.
- No interfaces → CGLIB regardless.
You force CGLIB explicitly with @EnableAspectJAutoProxy(proxyTargetClass = true).