skip to content

When would you choose a JDK dynamic proxy versus a bytecode library like CGLIB or ByteBuddy?

level: seniorimportance: should knowfreq 38%

answer

  1. JDK proxy = implements interfaces, no deps
  2. CGLIB/ByteBuddy = generate a subclass
  3. Can't subclass final classes/methods
  4. Spring: interface→JDK, class→CGLIB
  5. ByteBuddy is the modern default (Mockito/Hibernate)

basics

~20 s

Use 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 s

JDK 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

for a junior

Knows JDK proxies need an interface and that other tools exist for classes.

for a middle

Can state the interface-vs-subclass distinction and name CGLIB/ByteBuddy as class proxying options.

for a senior

Articulates the trade-offs (deps, final, constructors, module system) and explains Spring's automatic JDK-vs-CGLIB selection.

for a principal

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)

context