spring-projects/spring-framework · error · AmbiguousBindingException

Found {} candidate variable names but only one candidate bin

Error message

Found {} candidate variable names but only one candidate binding slot when matching primitive args

What it means

Thrown when the args() clause of the pointcut yields multiple candidate variable names for primitive binding but only one unbound primitive slot exists. Spring cannot decide which name maps to the single primitive parameter. Only surfaces when raiseExceptions=true.

Source

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

	private void maybeBindPrimitiveArgsFromPointcutExpression() {
		int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments();
		if (numUnboundPrimitives > 1) {
			throw new AmbiguousBindingException("Found " + numUnboundPrimitives +
					" unbound primitive arguments with no way to distinguish between them.");
		}
		if (numUnboundPrimitives == 1) {
			// Look for arg variable and bind it if we find exactly one...
			List<String> varNames = new ArrayList<>();
			String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
			for (int i = 0; i < tokens.length; i++) {
				if (tokens[i].equals("args") || tokens[i].startsWith("args(")) {
					PointcutBody body = getPointcutBody(tokens, i);
					i += body.numTokensConsumed;
					maybeExtractVariableNamesFromArgs(body.text, varNames);
				}
			}
			if (varNames.size() > 1) {
				throw new AmbiguousBindingException("Found " + varNames.size() +
						" candidate variable names but only one candidate binding slot when matching primitive args");
			}
			else if (varNames.size() == 1) {
				// 1 primitive arg, and one candidate...
				for (int i = 0; i < this.argumentTypes.length; i++) {
					if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) {
						bindParameterName(i, varNames.get(0));
						break;
					}
				}
			}
		}
	}

	/*
	 * Return true if the parameter name binding for the given parameter
	 * index has not yet been assigned.
	 */

View on GitHub (pinned to e8729d0438)

Solutions

  1. Match the number of args() binding variables to the number of primitive parameters in the advice.
  2. Use wildcards (*) in args() for parameters you do not want to bind.
  3. Compile with -parameters for full disambiguation.

Example fix

// before
@Before("execution(* com.acme.*.*(int,int)) && args(a, b)")
public void before(int a) { ... }

// after
@Before("execution(* com.acme.*.*(int,int)) && args(a, *)")
public void before(int a) { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Match args() primitive var count to unbound primitive param count:
int argsVars = countArgsBindingVars(pointcutExpression);
int unboundPrimitives = countUnboundPrimitiveParams(adviceMethod);
assert argsVars == unboundPrimitives;

Prevention

When it happens

Trigger: A pointcut with args(a, b) where both a and b are candidate names but only one primitive parameter is unbound.

Common situations: Pointcut args() lists more variables than the advice method has primitive parameters; mismatched refactor.

Related errors


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