spring-projects/spring-framework · error · AopConfigException
Only one afterThrowing method per specific Throwable…
Error message
Only one afterThrowing method per specific Throwable subclass allowed: {} / {} What it means
Thrown as AopConfigException by ThrowsAdviceInterceptor's constructor when two afterThrowing methods on the same advice are keyed to the same Throwable subclass. The exceptionHandlerMap (keyed by throwable parameter type) detected an existing entry at line 107-110, so the second registration is rejected to keep dispatch unambiguous.
Solutions
- Keep only one handler per Throwable type: pick either the one-arg or the four-arg form for a given exception class.
- If you need both context-rich and simple handling, choose the four-arg form and read context from its parameters.
- Register different methods for different exception subclasses (one handler per class is allowed).
Example fix
// before
public void afterThrowing(IllegalStateException ex) { ... }
public void afterThrowing(Method m, Object[] a, Object t, IllegalStateException ex) { ... } // duplicate
// after
public void afterThrowing(Method m, Object[] a, Object t, IllegalStateException ex) { ... }
public void afterThrowing(Method m, Object[] a, Object t, IllegalArgumentException ex) { ... } // distinct type Defensive patterns
Strategy: validation
Validate before calling
Map<Class<?>, Method> seen = new HashMap<>();
for (Method m : advice.getClass().getMethods()) {
if (!m.getName().equals("afterThrowing")) continue;
Class<?> exType = (m.getParameterCount() == 1) ? m.getParameterTypes()[0] : m.getParameterTypes()[3];
if (seen.put(exType, m) != null) {
throw new IllegalStateException("Duplicate afterThrowing for " + exType + ": " + m);
}
} Type guard
static boolean uniqueThrowablesPerMethod(Method[] ms) {
Set<Class<?>> seen = new HashSet<>();
for (Method m : ms) {
if (!m.getName().equals("afterThrowing")) continue;
Class<?> t = (m.getParameterCount() == 1) ? m.getParameterTypes()[0] : m.getParameterTypes()[3];
if (!seen.add(t)) return false;
}
return true;
} Try / catch
try { new ThrowsAdviceInterceptor(advice); }
catch (AopConfigException e) {
if (e.getMessage().contains("Only one afterThrowing method per specific Throwable")) { /* keep one handler per type */ }
throw e;
} Prevention
- Provide at most one afterThrowing handler per Throwable subclass.
- Use the four-arg form when you also need the one-arg behavior; do not declare both for the same type.
- Distinct exception subclasses can each have their own handler.
When it happens
Trigger: A ThrowsAdvice class declares two methods like 'afterThrowing(IllegalStateException ex)' and 'afterThrowing(Method, Object[], Object, IllegalStateException ex)'; both register under IllegalStateException and the second triggers the error.
Common situations: Adding the four-arg overload alongside the one-arg overload for the same exception type; merging two advice implementations that handle the same exception; refactoring that left a duplicate handler.
Related errors
- Invalid afterThrowing signature: four arguments must be…
- Invalid afterThrowing signature: single argument must be a…
- Unsupported afterThrowing signature: single throwable…
- 'argumentNames' property of AbstractAspectJAdvice contains…
- At least one handler method must be found in class
AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09).
Data as JSON: /api/errors/08162a1b5045c7c1.
Report an issue: GitHub.
Appendix: source
Thrown at spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java:109
}
else if (method.getParameterCount() == 4) {
// Method, Object[], target, throwable
Class<?>[] paramTypes = method.getParameterTypes();
if (!Method.class.equals(paramTypes[0]) || !Object[].class.equals(paramTypes[1]) ||
Throwable.class.equals(paramTypes[2]) || !Throwable.class.isAssignableFrom(paramTypes[3])) {
throw new AopConfigException("Invalid afterThrowing signature: " +
"four arguments must be Method, Object[], target, throwable: " + method);
}
throwableParam = paramTypes[3];
}
if (throwableParam == null) {
throw new AopConfigException("Unsupported afterThrowing signature: single throwable argument " +
"or four arguments Method, Object[], target, throwable expected: " + method);
}
// An exception handler to register...
Method existingMethod = this.exceptionHandlerMap.put(throwableParam, method);
if (existingMethod != null) {
throw new AopConfigException("Only one afterThrowing method per specific Throwable subclass " +
"allowed: " + method + " / " + existingMethod);
}
if (logger.isDebugEnabled()) {
logger.debug("Found exception handler method on throws advice: " + method);
}
}
}
if (this.exceptionHandlerMap.isEmpty()) {
throw new AopConfigException(
"At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
}
}
/**
* Return the number of handler methods in this advice.
*/View on GitHub (pinned to 69bf83ad71)