skip to content

How does Spring resolve the parameter names used in args() bindings, and what breaks binding?

level: middleimportance: should knowfreq 40%

answer

  1. names lost in bytecode by default
  2. -parameters flag, debug info, or argNames
  3. Kotlin keeps names; Boot enables -parameters
  4. bind by name not position
  5. JoinPoint param auto-supplied, not listed

basics

~20 s

Spring matches the name inside args(name) to an advice method parameter with the same name. It gets those names from the -parameters compiler flag, debug info, or the annotation's argNames attribute. If names aren't available, binding fails with an error.

solid answer

~30 s

When you write args(account), Spring must map that pointcut variable to an advice-method parameter. Java erases parameter names by default, so Spring's DefaultParameterNameDiscoverer relies on either the `-parameters` javac flag, debug line-number/local-variable tables (-g), or an explicit `argNames` attribute on the advice annotation. Kotlin retains parameter names, so it usually just works. If none of these are present, Spring throws IllegalArgumentException complaining it cannot discover parameter names, or it may mis-bind. Best practice: enable -parameters in the build (Spring Boot's Maven/Gradle plugins do this by default) or specify argNames explicitly for library aspects that must not depend on compiler flags.

code

java · 15 lines
java
@Aspect
@Component
public class ExplicitNamesAspect {

    // argNames makes binding work even without -parameters.
    // JoinPoint is auto-supplied and NOT listed in argNames.
    @AfterReturning(
        pointcut = "execution(* com.app..*(..)) && args(account)",
        returning = "result",
        argNames = "account,result")
    public void afterSave(JoinPoint jp, Account account, Object result) {
        // bound by NAME, so param order vs pointcut order is free
        log.info("{} returned {}", account.getId(), result);
    }
}

go deeper

for a junior

Know that names can be lost at compile time and the -parameters flag or argNames fixes it.

for a middle

Explain the discovery strategies and diagnose the classic CI-only parameter-name failure.

for a senior

Advise argNames for shipped libraries and understand binding is by-name with JoinPoint auto-supplied.

for a principal

Set org-wide build conventions (-parameters on) and design aspects resilient to unknown downstream compiler settings.

**The core problem.** In an expression like `@Before("execution(..) && args(account)")` the token `account` is a binding variable. Spring must connect it to a parameter of the advice method so it can pass the runtime argument in. But standard Java `.class` files do *not* retain method parameter names unless you ask for them — so Spring needs a strategy to recover names. **How Spring discovers names.** Spring uses a ParameterNameDiscoverer (e.g. DefaultParameterNameDiscoverer, which combines several strategies). Sources, in effect: 1. **`-parameters` compiler flag** — javac writes a MethodParameters attribute containing real names into the bytecode. This is the modern, recommended source. Spring Boot's build plugins enable it by default. 2. **Debug information (`-g`)** — the local variable table can yield names; less reliable and often stripped in optimized builds. 3. **`argNames` attribute** — every AspectJ advice annotation (@Before, @AfterReturning, etc.) accepts `argNames="account"`, an explicit comma-separated list telling Spring the parameter names in order. This makes the aspect independent of compiler flags — useful for shipped libraries. 4. **Kotlin** retains parameter names in metadata, so binding generally works without extra flags. **What breaks it.** If you compile without `-parameters`, without debug info, and without `argNames`, Spring cannot resolve the names and throws something like `IllegalArgumentException: Methods needs to have parameters named ...` or an AmbiguousBindingException. A subtle failure: if there is exactly one unbound advice parameter, Spring can sometimes infer it positionally, but with multiple bindings (e.g. args plus a returning value) ambiguity arises and you must be explicit. **JoinPoint parameter.** If the advice's first parameter is of type JoinPoint (or ProceedingJoinPoint for @Around), Spring supplies it automatically and it is *not* counted among the names to bind — you don't list it in argNames. Historically, when using argNames and also having a JoinPoint first param, older docs allowed omitting it from argNames. **Ordering rule.** The names inside the pointcut (args(a, b), returning="r", throwing="t") do not have to appear in the same order as advice parameters — Spring binds by name, not position. That is exactly why name resolution matters. **Practical guidance.** For application aspects on Spring Boot, do nothing special — `-parameters` is on. For aspects in a shared library that downstream teams recompile with unknown flags, set `argNames` explicitly to be safe. Watch for build setups (some IDE run configs, older Gradle) that strip `-parameters`; a passing test that suddenly fails with parameter-name errors after a build change is the classic symptom.

  • Your aspect works locally but throws a parameter-name error in CI. What is the likely cause?
    The CI build compiles without the -parameters flag (or strips debug info) and the advice has no argNames, so Spring's ParameterNameDiscoverer can't map args(name) to the advice parameter. Add -parameters to the build or specify argNames.

saying these in an interview costs you the question

  • Assuming Java always keeps parameter names in bytecode.
  • Believing args and returning/throwing are bound positionally rather than by name.
  • Listing the JoinPoint parameter inside argNames.

context