spring-projects/spring-framework · error · IllegalArgumentException

Found candidate annotation binding variables but only one…

Error message

Found {} candidate annotation binding variables but only one potential argument binding slot

What it means

Thrown when the pointcut expression yields multiple annotation-binding variable names (candidate bindings from @annotation/@this/etc.) but the advice method has exactly one annotation-typed parameter slot. Spring cannot decide which candidate variable name to assign to the single slot. This is an IllegalArgumentException (not AmbiguousBindingException), signaling a structural mismatch between pointcut and advice.

Solutions

  1. Remove extra annotation-binding expressions from the pointcut so only one candidate variable remains
  2. Compile with -parameters to bypass name deduction entirely
  3. Ensure pointcut annotation designator arguments are fully-qualified type names when they represent types, not bindings

Example fix

// before
@Before("@annotation(a) && @this(b)")
public void before(MyAnnotation single) { ... } // 2 candidates, 1 slot

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

Strategy: validation

Validate before calling

// Check if the pointcut has more annotation binding variable names than the
// advice has annotation-typed params.
int annotationSlots = (int) Arrays.stream(adviceMethod.getParameterTypes())
    .filter(Annotation.class::isAssignableFrom).count();
// Crude count of annotation designator bindings in the expression:
long designatorCount = Arrays.stream(new String[]{"@annotation","@this","@target","@within","@withincode"})
    .filter(pointcutExpr::contains).count();
if (annotationSlots == 1 && designatorCount > 1) {
    throw new IllegalStateException("Multiple annotation designators but only one annotation param slot");
}

Try / catch

try {
    discoverer.getParameterNames(adviceMethod);
} catch (IllegalArgumentException ex) {
    // Multiple annotation binding candidates for one slot — simplify the pointcut
}

Prevention

When it happens

Trigger: A pointcut like @annotation(a) && @annotation(b) (two binding variable names) with an advice method that has only one annotation-typed parameter — e.g. void before(MyAnnotation single). Only fires when setRaiseExceptions(true).

Common situations: Over-specifying annotation bindings in a pointcut when the advice only needs one, or pointcut designator arguments that look like variable names but are intended as type names.

Related errors


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

Appendix: source

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

	 * Match the given list of extracted variable names to argument slots.
	 */
	private void bindAnnotationsFromVarNames(List<String> varNames) {
		if (!varNames.isEmpty()) {
			// we have work to do...
			int numAnnotationSlots = countNumberOfUnboundAnnotationArguments();
			if (numAnnotationSlots > 1) {
				throw new AmbiguousBindingException("Found " + varNames.size() +
						" potential annotation variable(s) and " +
						numAnnotationSlots + " potential argument slots");
			}
			else if (numAnnotationSlots == 1) {
				if (varNames.size() == 1) {
					// it's a match
					findAndBind(Annotation.class, varNames.get(0));
				}
				else {
					// multiple candidate vars, but only one slot
					throw new IllegalArgumentException("Found " + varNames.size() +
							" candidate annotation binding variables" +
							" but only one potential argument binding slot");
				}
			}
			else {
				// no slots so presume those candidate vars were actually type names
			}
		}
	}

	/**
	 * If the token starts meets Java identifier conventions, it's in.
	 */
	private @Nullable String maybeExtractVariableName(@Nullable String candidateToken) {
		if (AspectJProxyUtils.isVariableName(candidateToken)) {
			return candidateToken;
		}
		return null;

View on GitHub (pinned to 69bf83ad71)