spring-projects/spring-framework · error · AopConfigException

Only one afterThrowing method per specific Throwable subclas

Error message

Only one afterThrowing method per specific Throwable subclass allowed: {method} / {existingMethod}

What it means

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.

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 e8729d0438)

Solutions

  1. Keep exactly one afterThrowing method per Throwable subclass (combine logic into a single method).
  2. If you need both 1-arg and 4-arg variants, key them on different exception subclasses.
  3. Remove duplicate handlers from superclasses or the class itself.

Example fix

// before
public void afterThrowing(IOException ex) { ... }
public void afterThrowing(Method m, Object[] a, Object t, IOException ex) { ... } // duplicate key

// after: keep one, branch inside
public void afterThrowing(Method m, Object[] a, Object t, IOException ex) { ... }
Defensive patterns

Strategy: validation

Validate before calling

java.util.Map<Class<?>, Long> counts = new java.util.HashMap<>();
for (Method m : advice.getClass().getMethods()) {
    if (!m.getName().equals("afterThrowing")) continue;
    Class<?> exType = m.getParameterCount() == 1 ? m.getParameterTypes()[0]
        : m.getParameterCount() == 4 ? m.getParameterTypes()[3] : null;
    if (exType != null) counts.merge(exType, 1L, Long::sum);
}
if (counts.values().stream().anyMatch(c -> c > 1)) {
    throw new IllegalStateException("Duplicate afterThrowing for the same Throwable subclass in " + advice.getClass());
}

Prevention

When it happens

Trigger: Defining two afterThrowing(Exception) methods, or afterThrowing(Method,Object[],Object,IOException) and afterThrowing(IOException) - both keyed by IOException - on the same advice class.

Common situations: Inheritance where a subclass redefines a parent afterThrowing for the same exception; copy-paste handlers; refactoring that introduced a duplicate.

Related errors


AI-assisted analysis of spring-projects/spring-framework@e8729d0438 (2026-08-04). Data as JSON: /data/errors/d76edfc760d22cda.json. Report an issue: GitHub.