When would you choose a JDK dynamic proxy versus a bytecode library like CGLIB or ByteBuddy?
answer
- JDK proxy = implements interfaces, no deps
- CGLIB/ByteBuddy = generate a subclass
- Can't subclass final classes/methods
- Spring: interface→JDK, class→CGLIB
- ByteBuddy is the modern default (Mockito/Hibernate)
basics
~20 sUse a JDK dynamic proxy when your target is behind an interface — it needs no extra libraries. Use CGLIB or ByteBuddy when you must proxy a concrete class with no interface, because JDK proxies only work with interfaces.
solid answer
~50 sJDK dynamic proxies are built into the JVM and require no dependencies, but they can only implement interfaces — the generated class already extends java.lang.reflect.Proxy. So if your target type only exists as a concrete class with no interface, JDK proxying can't help. That's where subclass-based bytecode libraries come in: CGLIB (older, asm-based) and ByteBuddy (modern, the de facto standard) generate a runtime subclass and override its methods. The catch is they can't override final classes or final/private methods, and the subclass must be instantiable. Spring famously chooses automatically: interface-based beans get JDK proxies, class-based beans get CGLIB. Trade-offs: JDK proxies are simpler, dependency-free, and align with 'program to interfaces'; bytecode proxies are more flexible but heavier, may need extra module/illegal-access configuration, and historically struggled with final and the Java module system. For most clean designs with interfaces, JDK proxies are the right default.
go deeper
Knows JDK proxies need an interface and that other tools exist for classes.
Can state the interface-vs-subclass distinction and name CGLIB/ByteBuddy as class proxying options.
Articulates the trade-offs (deps, final, constructors, module system) and explains Spring's automatic JDK-vs-CGLIB selection.
Weighs proxying strategy against architecture (interface extraction vs composition), library maintenance/risk, JDK module constraints, and performance on hot paths.
## Two fundamentally different proxying strategies There are two ways to manufacture a 'stand-in' object at runtime: 1. **Interface-based (JDK dynamic proxy)** — generate a class that *implements* the target interfaces. 2. **Subclass-based (bytecode libraries)** — generate a class that *extends* the target concrete class and overrides its methods. ### JDK dynamic proxy (java.lang.reflect.Proxy) - **Built into the JDK** — zero dependencies. - The generated class `extends java.lang.reflect.Proxy implements YourInterfaces`. Because Java has **single inheritance**, it cannot also extend an arbitrary class — hence **interfaces only**. - Every call routes to your `InvocationHandler`. - **Pros**: no library, no extra security/module config, encourages interface-oriented design, fast and well-understood. - **Cons**: useless if the target has no interface; you must know the interface set up front. ### CGLIB (Code Generation Library) - An older, **ASM**-based library that creates a **subclass** of the target class at runtime and overrides its non-final methods, routing them to a `MethodInterceptor`. - **Pros**: can proxy classes that have no interface. - **Cons**: cannot override **final** classes or **final/private** methods; the proxied subclass calls the **superclass constructor** (side effects can run twice, or fail if there's no suitable constructor); it bundles ASM; with newer JDKs and the module system it can hit illegal-access warnings/errors. CGLIB is largely in maintenance mode. ### ByteBuddy - The **modern** code-generation library, now the default behind Mockito, Hibernate, and increasingly Spring tooling. Same subclassing approach but a cleaner API, active maintenance, and better support for recent JDKs and modules. - Same fundamental limitation: it generates a subclass, so **final classes/methods can't be intercepted**. ### How Spring decides (a concrete mental model) Spring AOP picks automatically: if a bean is referenced through an **interface**, Spring uses a **JDK dynamic proxy**; if it's a **concrete class** (or you set `proxyTargetClass=true`), it uses **CGLIB**. This is why injecting a Spring bean by its concrete class type, or self-invocation inside a class, sometimes surprises people — the proxy semantics differ. ### Decision checklist - Target has a clean interface and you control the design → **JDK dynamic proxy** (default). - Target is a concrete class you can't or won't extract an interface from → **ByteBuddy** (preferred) or **CGLIB**. - Target or the method is `final`, or the class lacks an accessible constructor → **neither subclass approach works**; reconsider the design (e.g. composition/decorator). - You want zero dependencies and module-system friendliness → **JDK dynamic proxy**. ### Performance note Both approaches generate and load a class once, then reuse it; per-call overhead is the reflective/interceptor dispatch. JDK proxy method dispatch and class caching have improved over JDK versions. For hot paths, the indirection cost is usually negligible but measurable — benchmark if it matters.
- Why can't CGLIB or ByteBuddy proxy a final class?Both work by generating a runtime subclass and overriding methods. A final class cannot be subclassed and a final method cannot be overridden, so there is nothing to intercept.
- How does Spring choose between a JDK proxy and CGLIB?By default Spring uses a JDK dynamic proxy when the bean is accessed through an interface, and CGLIB when it's a concrete class or proxyTargetClass=true is set.
saying these in an interview costs you the question
- Saying CGLIB can proxy final classes or final methods
- Claiming JDK proxies can wrap a concrete class without an interface
- Forgetting subclass proxies invoke the superclass constructor
- Treating CGLIB as the modern standard (ByteBuddy now is)