spring-projects/spring-framework · error · AmbiguousBindingException

Found candidate variable names but only one candidate…

Error message

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

What it means

Thrown when exactly one primitive argument slot remains but the args() expression in the pointcut yields multiple candidate variable names. Spring cannot determine which candidate binds to the single primitive slot.

Solutions

  1. Compile with -parameters to resolve names directly
  2. Reduce args() in the pointcut to a single binding variable
  3. Add the matching number of parameters to the advice method

Example fix

// before
@Before("args(a, b)")
public void before(int a) { ... } // 2 candidates, 1 primitive slot

// after
@Before("args(a)")
public void before(int a) { ... }
Defensive patterns

Strategy: validation

Validate before calling

// If the advice has exactly one primitive param, ensure args() in the pointcut has at most one binding variable
int primitiveSlots = (int) Arrays.stream(adviceMethod.getParameterTypes())
    .filter(Class::isPrimitive).count();
if (primitiveSlots == 1 && pointcutExpr.contains("args(")) {
    // Count binding variables in args(...) — if >1, this will throw
    logger.warn("Multiple args() binding variables with one primitive slot may be ambiguous");
}

Try / catch

try {
    discoverer.getParameterNames(adviceMethod);
} catch (AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException ex) {
    // Reduce args() to a single binding variable, or add params
}

Prevention

When it happens

Trigger: A pointcut like args(a, b) with an advice method that has exactly one primitive parameter — e.g. void before(int a). The algorithm finds two candidate variable names but only one primitive slot. Only fires when setRaiseExceptions(true).

Common situations: Over-specified args() binding with fewer advice parameters than bindings, compiled without -parameters.

Related errors


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

Appendix: 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 69bf83ad71)