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

  1. 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
  2. Reduce the advice method to exactly one unbound parameter (plus optional JoinPoint as the first param) for the returning binding
  3. 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

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


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:
	 * &#64;this, &#64;target, &#64;args, &#64;within, &#64;withincode, &#64;annotation.
	 * If we find one of these pointcut expressions, try and extract a candidate variable

View on GitHub (pinned to 69bf83ad71)