{"id":"d76edfc760d22cda","repo":"spring-projects/spring-framework","slug":"only-one-afterthrowing-method-per-specific-throwab","errorCode":null,"errorMessage":"Only one afterThrowing method per specific Throwable subclass allowed: {method} / {existingMethod}","messagePattern":"Only one afterThrowing method per specific Throwable subclass allowed: (.+?) / (.+?)","errorType":"exception","errorClass":"AopConfigException","httpStatus":null,"severity":"error","filePath":"spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java","lineNumber":109,"sourceCode":"\t\t\t\t}\n\t\t\t\telse if (method.getParameterCount() == 4) {\n\t\t\t\t\t// Method, Object[], target, throwable\n\t\t\t\t\tClass<?>[] paramTypes = method.getParameterTypes();\n\t\t\t\t\tif (!Method.class.equals(paramTypes[0]) || !Object[].class.equals(paramTypes[1]) ||\n\t\t\t\t\t\t\tThrowable.class.equals(paramTypes[2]) || !Throwable.class.isAssignableFrom(paramTypes[3])) {\n\t\t\t\t\t\tthrow new AopConfigException(\"Invalid afterThrowing signature: \" +\n\t\t\t\t\t\t\t\t\"four arguments must be Method, Object[], target, throwable: \" + method);\n\t\t\t\t\t}\n\t\t\t\t\tthrowableParam = paramTypes[3];\n\t\t\t\t}\n\t\t\t\tif (throwableParam == null) {\n\t\t\t\t\tthrow new AopConfigException(\"Unsupported afterThrowing signature: single throwable argument \" +\n\t\t\t\t\t\t\t\"or four arguments Method, Object[], target, throwable expected: \" + method);\n\t\t\t\t}\n\t\t\t\t// An exception handler to register...\n\t\t\t\tMethod existingMethod = this.exceptionHandlerMap.put(throwableParam, method);\n\t\t\t\tif (existingMethod != null) {\n\t\t\t\t\tthrow new AopConfigException(\"Only one afterThrowing method per specific Throwable subclass \" +\n\t\t\t\t\t\t\t\"allowed: \" + method + \" / \" + existingMethod);\n\t\t\t\t}\n\t\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\t\tlogger.debug(\"Found exception handler method on throws advice: \" + method);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.exceptionHandlerMap.isEmpty()) {\n\t\t\tthrow new AopConfigException(\n\t\t\t\t\t\"At least one handler method must be found in class [\" + throwsAdvice.getClass() + \"]\");\n\t\t}\n\t}\n\n\n\t/**\n\t * Return the number of handler methods in this advice.\n\t */","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/spring-projects/spring-framework/blob/e8729d043887bf0d0baf91e062e909b56eb2b708/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java#L91-L127","documentation":"exceptionHandlerMap keys handlers by Throwable subclass. If two afterThrowing methods accept the same (or equal) Throwable subclass, the second registration evicts the first and Spring throws AopConfigException naming both the new and existing methods - dispatch would otherwise be ambiguous.","triggerScenarios":"Defining two afterThrowing(Exception) methods, or afterThrowing(Method,Object[],Object,IOException) and afterThrowing(IOException) - both keyed by IOException - on the same advice class.","commonSituations":"Inheritance where a subclass redefines a parent afterThrowing for the same exception; copy-paste handlers; refactoring that introduced a duplicate.","solutions":["Keep exactly one afterThrowing method per Throwable subclass (combine logic into a single method).","If you need both 1-arg and 4-arg variants, key them on different exception subclasses.","Remove duplicate handlers from superclasses or the class itself."],"exampleFix":"// before\npublic void afterThrowing(IOException ex) { ... }\npublic void afterThrowing(Method m, Object[] a, Object t, IOException ex) { ... } // duplicate key\n\n// after: keep one, branch inside\npublic void afterThrowing(Method m, Object[] a, Object t, IOException ex) { ... }","handlingStrategy":"validation","validationCode":"java.util.Map<Class<?>, Long> counts = new java.util.HashMap<>();\nfor (Method m : advice.getClass().getMethods()) {\n    if (!m.getName().equals(\"afterThrowing\")) continue;\n    Class<?> exType = m.getParameterCount() == 1 ? m.getParameterTypes()[0]\n        : m.getParameterCount() == 4 ? m.getParameterTypes()[3] : null;\n    if (exType != null) counts.merge(exType, 1L, Long::sum);\n}\nif (counts.values().stream().anyMatch(c -> c > 1)) {\n    throw new IllegalStateException(\"Duplicate afterThrowing for the same Throwable subclass in \" + advice.getClass());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep one afterThrowing method per Throwable subclass.","Combine related logic into a single handler rather than overloading.","Watch for inherited afterThrowing methods that duplicate subclass ones."],"tags":["aop","throws-advice","duplicate","configuration"],"analyzedSha":"e8729d043887bf0d0baf91e062e909b56eb2b708","analyzedAt":"2026-08-04T19:07:39.725Z","schemaVersion":2}