{"record":{"id":"f4128ccd26861112","repo":"spring-projects/spring-framework","slug":"binding-of-throwing-parameter-is-ambiguous-c","errorCode":null,"errorMessage":"Binding of throwing parameter '{}' is ambiguous: could be bound to argument {} or {}","messagePattern":"Binding of throwing parameter '(.+?)' is ambiguous: could be bound to argument (.+?) or (.+?)","errorType":"exception","errorClass":"AmbiguousBindingException","httpStatus":null,"severity":"error","filePath":"spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java","lineNumber":343,"sourceCode":"\t/**\n\t * If a throwing name was specified and there is exactly one choice remaining\n\t * (argument that is a subtype of Throwable) then bind it.\n\t */\n\tprivate void maybeBindThrowingVariable() {\n\t\tif (this.throwingName == null) {\n\t\t\treturn;\n\t\t}\n\n\t\t// So there is binding work to do...\n\t\tint throwableIndex = -1;\n\t\tfor (int i = 0; i < this.argumentTypes.length; i++) {\n\t\t\tif (isUnbound(i) && isSubtypeOf(Throwable.class, i)) {\n\t\t\t\tif (throwableIndex == -1) {\n\t\t\t\t\tthrowableIndex = i;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Second candidate we've found - ambiguous binding\n\t\t\t\t\tthrow new AmbiguousBindingException(\"Binding of throwing parameter '\" +\n\t\t\t\t\t\t\tthis.throwingName + \"' is ambiguous: could be bound to argument \" +\n\t\t\t\t\t\t\tthrowableIndex + \" or \" + i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (throwableIndex == -1) {\n\t\t\tthrow new IllegalStateException(\"Binding of throwing parameter '\" + this.throwingName +\n\t\t\t\t\t\"' could not be completed as no available arguments are a subtype of Throwable\");\n\t\t}\n\t\telse {\n\t\t\tbindParameterName(throwableIndex, this.throwingName);\n\t\t}\n\t}\n\n\t/**\n\t * If a returning variable was specified and there is only one choice remaining, bind it.\n\t */","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/spring-projects/spring-framework/blob/69bf83ad716d0cfc4b0520a19b4d8b24c79d1538/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java#L325-L361","documentation":"Thrown as AmbiguousBindingException by maybeBindThrowingVariable (line 342-346) when a throwingName is set and the algorithm finds TWO OR MORE unbound parameters that are subtypes of Throwable. With multiple candidate slots, Spring cannot decide which one receives the thrown exception, so it refuses to guess. AmbiguousBindingException is a RuntimeException (line 748).","triggerScenarios":"An after-throwing advice method declares two Throwable subtype parameters (e.g. after(RuntimeException ex, IOException io)) with throwing=\"ex\", and the pointcut does not disambiguate which parameter is the bound exception. Without -parameters or arg-names, the discoverer matches by type and finds two candidates.","commonSituations":"Advice methods that log multiple exception types, or that accept a Throwable plus a context exception. Compiling without -parameters so name-based disambiguation is unavailable. Common when generalizing an after-throwing handler.","solutions":["Compile with -parameters so the throwing name can be matched to the specific parameter by name (the message suggests this).","Specify arg-names explicitly so the throwing parameter is identified.","Reduce the advice method to a single Throwable parameter.","Use distinct parameter types so only one matches the throwing type restriction."],"exampleFix":"// before — two Throwable subtypes, ambiguous, no -parameters\n@AfterThrowing(pointcut=\"execution(* svc.*(..))\", throwing=\"ex\")\npublic void onError(RuntimeException ex, IOException io) { ... }\n\n// after — single Throwable parameter, or compile with -parameters and name it 'ex'\n@AfterThrowing(pointcut=\"execution(* svc.*(..))\", throwing=\"ex\")\npublic void onError(RuntimeException ex) { ... }","handlingStrategy":"validation","validationCode":"import java.lang.reflect.Method;\n\n// Ensure at most one unbound Throwable parameter when throwing is set.\nvoid assertSingleThrowableCandidate(Method adviceMethod) {\n    int throwableParams = 0;\n    for (Class<?> t : adviceMethod.getParameterTypes()) {\n        if (Throwable.class.isAssignableFrom(t)) throwableParams++;\n    }\n    if (throwableParams > 1) {\n        throw new IllegalStateException(\"Ambiguous throwing binding: \" + throwableParams\n            + \" Throwable params. Compile with -parameters or supply arg-names.\");\n    }\n}","typeGuard":"boolean hasSingleThrowableParam(Method m) {\n    int count = 0;\n    for (Class<?> t : m.getParameterTypes()) if (Throwable.class.isAssignableFrom(t)) count++;\n    return count == 1;\n}","tryCatchPattern":"import org.springframework.aop.aspectj.AspectJAdviceParameterNameDiscoverer.AmbiguousBindingException;\ntry {\n    advice.getPointcut();\n} catch (AmbiguousBindingException ex) {\n    if (ex.getMessage().contains(\"throwing parameter\")) {\n        // reduce to one Throwable param, or compile with -parameters and name it\n    } else { throw ex; }\n}","preventionTips":["Declare a single Throwable parameter on after-throwing advice.","Compile with -parameters so the throwing name disambiguates by name.","Supply arg-names when multiple Throwable params are unavoidable."],"tags":["spring-aop","aspectj","after-throwing","ambiguous-binding","parameter-discovery"],"backgroundTag":null,"analyzedSha":"69bf83ad716d0cfc4b0520a19b4d8b24c79d1538","analyzedAt":"2026-08-09T15:32:58.770Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}