skip to content

Compare @EnableAsync's PROXY mode with mode=ASPECTJ. When would you choose ASPECTJ, and what's the cost?

level: principalimportance: nice to knowfreq 30%

answer

  1. PROXY = Spring AOP wrapper, external+public only
  2. ASPECTJ = bytecode weaving, self-invocation + non-public work
  3. ASPECTJ needs spring-aspects + CTW or LTW agent
  4. default is PROXY; restructure beans instead
  5. flipping mode can silently make helpers async

basics

~20 s

PROXY (default) wraps beans in a proxy, so only external calls to public methods become async. ASPECTJ weaves the async advice into the bytecode, so self-invocation and non-public methods also work. ASPECTJ needs the AspectJ weaver and compile/load-time weaving setup, which is more complex.

solid answer

~50 s

`@EnableAsync` defaults to `AdviceMode.PROXY`: Spring AOP wraps each `@Async` bean in a JDK or CGLIB proxy, and only calls that pass **through** that proxy — external calls to **public** methods — get dispatched to an executor. `AdviceMode.ASPECTJ` instead uses the `AnnotationAsyncExecutionAspect` woven directly into class bytecode via compile-time or load-time weaving (LTW). Because the advice lives in the method itself, `ASPECTJ` handles **self-invocation** and even **non-public** methods, and there's no proxy indirection at call time. The cost: you must add `spring-aspects`, enable weaving (an AspectJ compiler step or a `-javaagent`/`LoadTimeWeaver` for LTW), which complicates the build/runtime and can affect startup. Most applications stay on PROXY and simply structure beans to cross the proxy boundary; you reach for ASPECTJ when you genuinely can't restructure — e.g. legacy internal calls or non-public methods that must be async — and can afford the weaving setup.

code

java · 12 lines
java
// ASPECTJ mode: internal calls and non-public methods can be @Async,
// but you must add spring-aspects and enable weaving (LTW agent or ajc).
@Configuration
@EnableAsync(mode = AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving   // plus -javaagent:spring-instrument / aspectjweaver
public class AsyncConfig { }

@Service
class Reports {
    void handle() { generate(); }   // works async even as a this-call under ASPECTJ
    @Async void generate() { /* heavy work */ }
}

go deeper

for a junior

Aware there is a non-default ASPECTJ mode; details optional at this level.

for a middle

Knows ASPECTJ fixes self-invocation but requires weaving.

for a senior

Can articulate the PROXY vs ASPECTJ trade-offs and the weaving requirements.

for a principal

Makes the mode a considered architectural decision, weighing weaving cost vs eliminating proxy limitations, and audits call-site semantics when changing modes.

**The two advice modes.** `@EnableAsync(mode = ...)` accepts `org.springframework.context.annotation.AdviceMode`: - `AdviceMode.PROXY` (default) — Spring AOP. A `BeanPostProcessor` (`AsyncAnnotationBeanPostProcessor`) creates a proxy around each bean that has `@Async`. Interception happens in the proxy. - `AdviceMode.ASPECTJ` — uses AspectJ to **weave** the async advice (`org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect`, shipped in `spring-aspects`) into the target class's compiled bytecode. **PROXY characteristics.** - Only calls **through the proxy** are advised → external calls only; **self-invocation is not** async. - Only **public** methods are advised (JDK proxies expose interface methods; CGLIB can't override `final`/`private`). - Zero extra build tooling — pure runtime AOP. This is why it's the default and covers the vast majority of cases. **ASPECTJ characteristics.** - The advice is part of the method's own bytecode, so it fires regardless of **how** the method is reached: internal `this` calls, calls between methods of the same bean, and **non-public** methods all become async. - No per-call proxy indirection. - Requires: the `spring-aspects` dependency **and** a weaving mechanism — either **compile-time weaving** (the AspectJ compiler `ajc` / build plugin) or **load-time weaving (LTW)** via a Java agent (`-javaagent:aspectjweaver.jar`) or a Spring `LoadTimeWeaver` (`@EnableLoadTimeWeaving`). This is the real cost: extra build/runtime configuration, a heavier startup, and a steeper operational learning curve. **Decision guidance.** Default to **PROXY** and design around it: keep proxied concerns (`@Async`, `@Transactional`, `@Cacheable`) on **collaborator beans** so every advised call naturally crosses a proxy boundary; keep advised methods **public**. This avoids the self-invocation pitfall without any weaving machinery. Choose **ASPECTJ** only when: - you have legacy code with unavoidable internal calls to `@Async` methods that you can't refactor; - you need `@Async` on non-public methods; - or you already run AspectJ weaving for other reasons (e.g. domain-object `@Configurable`), so the incremental cost is low. **Interplay / gotchas.** - Switching to ASPECTJ changes semantics subtly: methods you *thought* were synchronous internal helpers may suddenly run on another thread. Audit call sites when flipping the mode. - The `proxyTargetClass` attribute on `@EnableAsync` only matters in PROXY mode (forces CGLIB even when interfaces exist); it's irrelevant under ASPECTJ. - ASPECTJ weaving is global to woven classes — you can't opt individual beans out the way you scope proxying. - Exception-handling wiring (the uncaught-exception handler) is configured the same way regardless of mode, but that mechanism is a separate concern from executor selection and weaving. **Bottom line.** ASPECTJ trades operational complexity for the elimination of proxy limitations (self-invocation, visibility). It's a deliberate, relatively rare architectural choice; PROXY plus good bean structuring is the pragmatic default.

  • Under PROXY mode, why can't @Async apply to a private method?
    Proxies advise via interface methods (JDK) or subclass overrides (CGLIB); private methods can't be overridden or exposed through an interface, so the proxy never intercepts them. AspectJ weaving embeds advice in the method itself and can handle private methods.
  • What's the operational cost that keeps most teams on PROXY mode?
    ASPECTJ requires the AspectJ weaver — either compile-time weaving (ajc/build plugin) or load-time weaving via a Java agent / LoadTimeWeaver. That adds build complexity, a startup weaving step, and operational overhead most apps don't need since restructuring beans solves the common cases.

saying these in an interview costs you the question

  • Claiming ASPECTJ mode needs no extra dependencies or weaving setup
  • Thinking proxyTargetClass changes anything under ASPECTJ
  • Assuming switching to ASPECTJ is behavior-neutral for existing internal calls

context