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
- Remove extra annotation-binding expressions from the pointcut so only one candidate variable remains
- Compile with -parameters to bypass name deduction entirely
- 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
- Compile with -parameters
- Keep annotation-binding designators in the pointcut to at most one when the advice has a single annotation param
- Use fully-qualified type names in designator arguments when they are types, not bindings
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
- Found potential annotation variable(s) and potential…
- Binding of returning parameter
- Found candidate reference pointcut variables but only one…
- Found candidate this(), target(), or args() variables but…
- Found candidate variable names but only one candidate…
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)