skip to content

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%

answer

  1. JDK proxy = interface only, not subclass
  2. instanceof Impl -> false
  3. BeanNotOfRequiredTypeException on class injection
  4. fix: inject interface OR force CGLIB
  5. non-interface methods not advised

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).

solid answer

~40 s

When Spring builds a JDK dynamic proxy, it generates a class implementing the target's interfaces via java.lang.reflect.Proxy. The proxy is assignable to those interfaces but is NOT a subclass of the concrete implementation, so proxy instanceof OrderServiceImpl is false. If another bean does @Autowired OrderServiceImpl, the container can't match the proxy to that type and wiring fails (no qualifying bean / BeanNotOfRequiredTypeException). The clean fix is to program to the interface: inject OrderService, not OrderServiceImpl. Alternatively force CGLIB with proxyTargetClass=true (Spring Boot already does this by default), producing a subclass proxy that IS assignable to the concrete class. Also remember JDK proxies only advise methods declared on interfaces, so a public method not on any interface won't be intercepted.

code

java · 17 lines
java
public interface OrderService { void place(); }

@Service
class OrderServiceImpl implements OrderService {
    @Transactional public void place() { /* ... */ }
}

@Component
class Checkout {
    // BREAKS under a JDK dynamic proxy: proxy is not an OrderServiceImpl
    // @Autowired OrderServiceImpl orderService;

    // WORKS: program to the interface
    private final OrderService orderService;
    Checkout(OrderService orderService) { this.orderService = orderService; }
}
// Alternative fix: @EnableTransactionManagement(proxyTargetClass = true) -> CGLIB subclass

go deeper

for a junior

Recognize that injecting the interface, not the impl, is the safe habit.

for a middle

Explain the instanceof relationship and the two fixes.

for a senior

Diagnose startup wiring failures and missing advice on non-interface methods from proxy choice.

for a principal

Set a team convention (interface-first vs CGLIB-everywhere) and codify it to prevent recurring class-injection incidents.

## The root cause A **JDK dynamic proxy** is created by `java.lang.reflect.Proxy.newProxyInstance(...)`. It manufactures a class that **implements a given set of interfaces** and routes every call through an `InvocationHandler`. Crucially, this generated class extends `java.lang.reflect.Proxy`, **not your target class**. So for `OrderServiceImpl implements OrderService`: - `proxy instanceof OrderService` -> **true** - `proxy instanceof OrderServiceImpl` -> **false** ## The failure symptom If some collaborator wires the bean by its concrete type: ```java @Autowired OrderServiceImpl orderService; // fails under a JDK proxy ``` Spring has replaced the raw `OrderServiceImpl` bean instance with a JDK proxy of type `OrderService`. There is no bean assignable to `OrderServiceImpl`, so you get `NoSuchBeanDefinitionException`/`BeanNotOfRequiredTypeException` at startup. The same applies to `applicationContext.getBean(OrderServiceImpl.class)` and to `@Qualifier`-by-type lookups. ## A second, subtler failure JDK proxies can only intercept methods **declared on an interface**. If `OrderServiceImpl` has a public method that is NOT part of any interface, calling it through the interface reference is impossible, and even reflective access won't carry the advice — so `@Transactional`/aspect behavior is missing for such methods. ## Fixes 1. **Program to the interface (preferred).** Inject `OrderService`, return interface types, and declare all advised methods on the interface. This is idiomatic Spring and keeps beans mockable. 2. **Force CGLIB** with `proxyTargetClass=true` on the relevant `@Enable...` annotation, or globally via `spring.aop.proxy-target-class=true`. CGLIB **subclasses** the target, so `proxy instanceof OrderServiceImpl` is true and class injection works. Spring Boot already defaults this on, which is exactly why most Boot developers never hit this pitfall. ## Related gotcha (not the same thing) **Self-invocation** — an internal `this.otherMethod()` call bypasses the proxy under BOTH JDK and CGLIB, because the call never leaves the target instance. That is a separate issue from the injection-by-class problem and is not solved by changing proxy type. ## When to accept JDK proxies JDK proxies are perfectly fine (and arguably cleaner) when you consistently code to interfaces. Choose them deliberately if you want to enforce interface boundaries and avoid subclassing `final` classes; just make sure nobody injects by impl type.

  • Does forcing CGLIB fix a method that isn't intercepted because it's called via this.method()?
    No. Self-invocation bypasses the proxy under both JDK and CGLIB. Fixes are refactoring the call out, self-injecting the proxy, or AspectJ load-time weaving.
  • Under a JDK proxy, why might a @Transactional public method never open a transaction?
    If that method isn't declared on any implemented interface, the JDK proxy can't intercept it, so the transactional advice never runs. Put it on the interface or use CGLIB.

saying these in an interview costs you the question

  • Saying a JDK proxy is a subclass of the target class
  • Believing proxyTargetClass fixes self-invocation
  • Thinking class injection always works regardless of proxy type
  • Assuming JDK proxies advise non-interface methods

context