spring-projects/spring-framework · error · AmbiguousBindingException
Binding of returning parameter
Error message
Binding of returning parameter '{}' is ambiguous: there are {} candidates. Consider compiling with -parameters in order to make declared parameter names available. What it means
Thrown by AspectJAdviceParameterNameDiscoverer when an @AfterReturning advice with a 'returning' attribute has more than one unbound parameter at the returning-binding stage. Spring cannot determine which parameter should receive the method's return value. This discoverer deduces parameter names from the pointcut expression when real names aren't in the bytecode; with multiple unbound candidates the deduction is ambiguous.
Solutions
- Compile with -parameters (javac -parameters, or maven-compiler-plugin <parameters>true</parameters>) so real parameter names are available and Spring does not need to deduce them
- Reduce the advice method to exactly one unbound parameter (plus optional JoinPoint as the first param) for the returning binding
- Add an explicit pointcut binding (args/this/target) for each extra parameter so only one remains for returning
Example fix
// before
@AfterReturning(pointcut = "execution(* com.example..*.*(..))", returning = "r")
public void after(Object r, Object extra) { ... } // ambiguous: two unbound
// after
@AfterReturning(pointcut = "execution(* com.example..*.*(..))", returning = "r")
public void after(Object r) { ... } Defensive patterns
Strategy: validation
Validate before calling
// Before deploying advice, verify the @AfterReturning method has at most one
// unbound param beyond JoinPoint when 'returning' is set.
Method advice = MyAspect.class.getDeclaredMethod("after", JoinPoint.class, Object.class, Object.class);
int unbound = advice.getParameterCount();
Class<?>[] types = advice.getParameterTypes();
if (types.length > 0 && (types[0] == JoinPoint.class || types[0] == ProceedingJoinPoint.class)) {
unbound--; // JoinPoint auto-binds
}
if (unbound > 1) {
throw new IllegalStateException(
"@AfterReturning advice has " + unbound + " unbound params; compile with -parameters or reduce params");
} Try / catch
AspectJAdviceParameterNameDiscoverer d = new AspectJAdviceParameterNameDiscoverer(expr);
d.setRaiseExceptions(true);
try {
String[] names = d.getParameterNames(adviceMethod);
} catch (AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException ex) {
// Deduction failed — fall back to -parameters based discovery or fix the advice signature
logger.warn("Returning parameter binding ambiguous; compile with -parameters", ex);
} Prevention
- Always compile with -parameters (javac -parameters / maven-compiler-plugin <parameters>true</parameters>)
- Design @AfterReturning advice with exactly one parameter for the returning binding plus an optional JoinPoint
When it happens
Trigger: An @AfterReturning(pointcut="...", returning="r") advice method with two or more unbound parameters after JoinPoint/throwing stages — e.g. void after(Object r, Object extra) where both r and extra are unbound. Only fires when setRaiseExceptions(true) is set; otherwise returns null silently.
Common situations: Advice compiled without the -parameters javac flag (parameter names stripped from bytecode), advice methods with extra parameters not referenced by the pointcut, or mixing a returning parameter with other untyped parameters.
Related errors
- Found candidate annotation binding variables but only one…
- Found candidate reference pointcut variables but only one…
- Found candidate this(), target(), or args() variables but…
- Found candidate variable names but only one candidate…
- Found potential annotation variable(s) and potential…
AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09).
Data as JSON: /api/errors/eb95bfafd84b5086.
Report an issue: GitHub.
Appendix: source
Thrown at spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java:370
"' 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.
*/
private void maybeBindReturningVariable() {
if (this.numberOfRemainingUnboundArguments == 0) {
throw new IllegalStateException(
"Algorithm assumes that there must be at least one unbound parameter on entry to this method");
}
if (this.returningName != null) {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates. " +
"Consider compiling with -parameters in order to make declared parameter names available.");
}
// We're all set... find the unbound parameter, and bind it.
for (int i = 0; i < this.parameterNameBindings.length; i++) {
if (this.parameterNameBindings[i] == null) {
bindParameterName(i, this.returningName);
break;
}
}
}
}
/**
* Parse the string pointcut expression looking for:
* @this, @target, @args, @within, @withincode, @annotation.
* If we find one of these pointcut expressions, try and extract a candidate variableView on GitHub (pinned to 69bf83ad71)