spring-projects/spring-framework · error · AmbiguousBindingException

Still unbound args at this()/target()/args() binding stage…

Error message

Still {} unbound args at this()/target()/args() binding stage, with no way to determine between them

What it means

Thrown by AspectJAdviceParameterNameDiscoverer at the this()/target()/args() binding stage when more than one argument remains unbound after all prior stages (JoinPoint, throwing, annotation, returning, primitive). The algorithm can bind at most one remaining slot from this()/target()/args(), so it cannot proceed with two or more unbound arguments.

Solutions

  1. Compile with -parameters so real parameter names are available
  2. Add explicit bindings (this/target/args) in the pointcut for each remaining parameter
  3. Reduce the advice method parameters so at most one is unbound by this stage

Example fix

// before
@Before("execution(* *(..)) && target(t)")
public void before(Object t, Object extra) { ... } // 2 unbound at this stage

// after
@Before("execution(* *(..)) && target(t) && args(extra)")
public void before(Object t, Object extra) { ... } // both bound
Defensive patterns

Strategy: validation

Validate before calling

// Count remaining unbound params after accounting for JoinPoint, returning, throwing
int unbound = adviceMethod.getParameterCount();
Class<?>[] types = adviceMethod.getParameterTypes();
if (types.length > 0 && (types[0] == JoinPoint.class || types[0] == ProceedingJoinPoint.class)) unbound--;
if (returningName != null) unbound--;
if (throwingName != null) unbound--;
if (unbound > 1) {
    throw new IllegalStateException(unbound + " params remain unbound; add -parameters or more bindings");
}

Try / catch

try {
    discoverer.getParameterNames(adviceMethod);
} catch (AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException ex) {
    // Too many unbound params — add explicit bindings or compile with -parameters
}

Prevention

When it happens

Trigger: An advice method with two or more unbound parameters after prior binding stages whose pointcut uses this()/target()/args() designators — e.g. @Before("execution(* *(..)) && target(t)") void before(Object t, Object other). Only fires when setRaiseExceptions(true).

Common situations: Advice methods with more parameters than the pointcut can bind, especially compiled without -parameters.

Related errors


AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09). Data as JSON: /api/errors/b2332982c70f003d. Report an issue: GitHub.

Appendix: source

Thrown at spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java:483

			return;
		}
		String[] tokens = StringUtils.tokenizeToStringArray(argsSpec, ",");
		for (int i = 0; i < tokens.length; i++) {
			tokens[i] = tokens[i].strip();
			String varName = maybeExtractVariableName(tokens[i]);
			if (varName != null) {
				varNames.add(varName);
			}
		}
	}

	/**
	 * Parse the string pointcut expression looking for this(), target() and args() expressions.
	 * If we find one, try and extract a candidate variable name and bind it.
	 */
	private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
		if (this.numberOfRemainingUnboundArguments > 1) {
			throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
					" unbound args at this()/target()/args() binding stage, with no way to determine between them");
		}

		List<String> varNames = new ArrayList<>();
		String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
		for (int i = 0; i < tokens.length; i++) {
			if (tokens[i].equals("this") ||
					tokens[i].startsWith("this(") ||
					tokens[i].equals("target") ||
					tokens[i].startsWith("target(")) {
				PointcutBody body = getPointcutBody(tokens, i);
				i += body.numTokensConsumed;
				String varName = maybeExtractVariableName(body.text);
				if (varName != null) {
					varNames.add(varName);
				}
			}
			else if (tokens[i].equals("args") || tokens[i].startsWith("args(")) {

View on GitHub (pinned to 69bf83ad71)