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

  1. Keep only one handler per Throwable type: pick either the one-arg or the four-arg form for a given exception class.
  2. If you need both context-rich and simple handling, choose the four-arg form and read context from its parameters.
  3. 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

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


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)