spring-projects/spring-framework · error · AmbiguousBindingException
Binding of throwing parameter
Error message
Binding of throwing parameter '{}' is ambiguous: could be bound to argument {} or {} What it means
Thrown as AmbiguousBindingException by maybeBindThrowingVariable (line 342-346) when a throwingName is set and the algorithm finds TWO OR MORE unbound parameters that are subtypes of Throwable. With multiple candidate slots, Spring cannot decide which one receives the thrown exception, so it refuses to guess. AmbiguousBindingException is a RuntimeException (line 748).
Solutions
- Compile with -parameters so the throwing name can be matched to the specific parameter by name (the message suggests this).
- Specify arg-names explicitly so the throwing parameter is identified.
- Reduce the advice method to a single Throwable parameter.
- Use distinct parameter types so only one matches the throwing type restriction.
Example fix
// before — two Throwable subtypes, ambiguous, no -parameters
@AfterThrowing(pointcut="execution(* svc.*(..))", throwing="ex")
public void onError(RuntimeException ex, IOException io) { ... }
// after — single Throwable parameter, or compile with -parameters and name it 'ex'
@AfterThrowing(pointcut="execution(* svc.*(..))", throwing="ex")
public void onError(RuntimeException ex) { ... } Defensive patterns
Strategy: validation
Validate before calling
import java.lang.reflect.Method;
// Ensure at most one unbound Throwable parameter when throwing is set.
void assertSingleThrowableCandidate(Method adviceMethod) {
int throwableParams = 0;
for (Class<?> t : adviceMethod.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(t)) throwableParams++;
}
if (throwableParams > 1) {
throw new IllegalStateException("Ambiguous throwing binding: " + throwableParams
+ " Throwable params. Compile with -parameters or supply arg-names.");
}
} Type guard
boolean hasSingleThrowableParam(Method m) {
int count = 0;
for (Class<?> t : m.getParameterTypes()) if (Throwable.class.isAssignableFrom(t)) count++;
return count == 1;
} Try / catch
import org.springframework.aop.aspectj.AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException;
try {
advice.getPointcut();
} catch (AmbiguousBindingException ex) {
if (ex.getMessage().contains("throwing parameter")) {
// reduce to one Throwable param, or compile with -parameters and name it
} else { throw ex; }
} Prevention
- Declare a single Throwable parameter on after-throwing advice.
- Compile with -parameters so the throwing name disambiguates by name.
- Supply arg-names when multiple Throwable params are unavoidable.
When it happens
Trigger: An after-throwing advice method declares two Throwable subtype parameters (e.g. after(RuntimeException ex, IOException io)) with throwing="ex", and the pointcut does not disambiguate which parameter is the bound exception. Without -parameters or arg-names, the discoverer matches by type and finds two candidates.
Common situations: Advice methods that log multiple exception types, or that accept a Throwable plus a context exception. Compiling without -parameters so name-based disambiguation is unavailable. Common when generalizing an after-throwing handler.
Related errors
- Binding of throwing parameter
- An advice method can never be a constructor
- Failed to bind all argument names
- Not enough arguments in method to satisfy binding of…
- Only afterThrowing advice can be used to bind a thrown…
AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09).
Data as JSON: /api/errors/f4128ccd26861112.
Report an issue: GitHub.
Appendix: source
Thrown at spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java:343
/**
* If a throwing name was specified and there is exactly one choice remaining
* (argument that is a subtype of Throwable) then bind it.
*/
private void maybeBindThrowingVariable() {
if (this.throwingName == null) {
return;
}
// So there is binding work to do...
int throwableIndex = -1;
for (int i = 0; i < this.argumentTypes.length; i++) {
if (isUnbound(i) && isSubtypeOf(Throwable.class, i)) {
if (throwableIndex == -1) {
throwableIndex = i;
}
else {
// Second candidate we've found - ambiguous binding
throw new AmbiguousBindingException("Binding of throwing parameter '" +
this.throwingName + "' is ambiguous: could be bound to argument " +
throwableIndex + " or " + i);
}
}
}
if (throwableIndex == -1) {
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName +
"' could not be completed as no available arguments are a subtype of Throwable");
}
else {
bindParameterName(throwableIndex, this.throwingName);
}
}
/**
* If a returning variable was specified and there is only one choice remaining, bind it.
*/View on GitHub (pinned to 69bf83ad71)