spring-projects/spring-framework · error · AmbiguousBindingException

Binding of throwing parameter '{}' is ambiguous: could be bo

Error message

Binding of throwing parameter '{}' is ambiguous: could be bound to argument {} or {}

What it means

Thrown by maybeBindThrowingVariable (line 342-346) as an AmbiguousBindingException when throwingName is set and there are two or more unbound advice parameters that are subtypes of Throwable. The heuristic cannot decide which one binds the thrown exception, so it refuses rather than guess.

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 e8729d0438)

Solutions

  1. Compile with -parameters so the discoverer binds by the actual parameter name matching the throwing attribute.
  2. Supply explicit arg-names so the throwing name maps to the intended parameter.
  3. Reduce the advice method to a single Throwable-typed parameter.

Example fix

// before
@AfterThrowing(value = "...", throwing = "ex")
public void onThrow(RuntimeException ex, Exception ctx) { ... } // compiled w/o -parameters
// after
@AfterThrowing(value = "...", throwing = "ex", argNames = "ex,ctx")
public void onThrow(RuntimeException ex, Exception ctx) { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Before wiring after-throwing, ensure at most one unbound Throwable param unless names are known.
long throwableParams = Arrays.stream(adviceMethod.getParameterTypes())
    .filter(Throwable.class::isAssignableFrom).count();
boolean namesAvailable = new DefaultParameterNameDiscoverer().getParameterNames(adviceMethod) != null;
if (throwingName != null && throwableParams > 1 && !namesAvailable) {
    throw new IllegalStateException("Ambiguous throwing binding on " + adviceMethod
        + " — supply arg-names or compile with -parameters");
}

Prevention

When it happens

Trigger: An after-throwing advice method declares multiple Throwable subtypes (e.g. RuntimeException and Exception) and throwing='ex' is set, while parameter names aren't available (compiled without -parameters) so the discoverer falls back to type-based matching and finds multiple candidates.

Common situations: Advice methods with more than one exception-typed parameter; mixing a generic Throwable param with a specific exception type; bytecode without parameter names forcing type-based binding.

Related errors


AI-assisted analysis of spring-projects/spring-framework@e8729d0438 (2026-08-04). Data as JSON: /data/errors/f4128ccd26861112.json. Report an issue: GitHub.