spring-projects/spring-framework · error · AopConfigException

At least one handler method must be found in class

Error message

At least one handler method must be found in class [{}]

What it means

Thrown by ThrowsAdviceInterceptor's constructor when it scans the supplied ThrowsAdvice object's public methods and finds none that match the required after-throwing handler signature. Valid handlers must be named 'afterThrowing' and take either a single Throwable subclass argument or four arguments (Method, Object[], target, ThrowableSubclass). Without at least one valid handler the interceptor has nothing to invoke on exceptions, so construction is rejected.

Solutions

  1. Add a public 'void afterThrowing(Exception ex)' (or any Throwable subclass) method to your ThrowsAdvice implementation.
  2. If you need full context, use the four-arg form: 'void afterThrowing(Method method, Object[] args, Object target, Exception ex)'.
  3. Verify the method name is exactly 'afterThrowing' and the parameter types match the supported signatures (single Throwable, or Method/Object[]/Object/Throwable).
  4. Make the method public so reflection via getClass().getMethods() can see it.

Example fix

// before
public class MyThrowsAdvice implements ThrowsAdvice {
    // no methods defined
}

// after
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception ex) {
        // handle exception
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify a ThrowsAdvice implementation has at least one valid handler before wiring
public static boolean hasValidAfterThrowingHandler(Object advice) {
    for (Method m : advice.getClass().getMethods()) {
        if (!"afterThrowing".equals(m.getName())) continue;
        Class<?>[] p = m.getParameterTypes();
        if (p.length == 1 && Throwable.class.isAssignableFrom(p[0])) return true;
        if (p.length == 4 && Method.class == p[0] && Object[].class == p[1]
                && Throwable.class.isAssignableFrom(p[3])) return true;
    }
    return false;
}

Type guard

// ThrowsAdvice is a marker interface; guard on method shape instead
static boolean isValidThrowsAdvice(Object o) {
    return o != null && hasValidAfterThrowingHandler(o);
}

Prevention

When it happens

Trigger: Instantiating 'new ThrowsAdviceInterceptor(advice)' (directly or via an Advisor/advice registration) where 'advice' has no public method named 'afterThrowing', or where the afterThrowing methods have an unsupported parameter count (e.g. 0, 2, or 3 args). Also triggered if every candidate method is rejected for an invalid signature, leaving the internal exceptionHandlerMap empty (line 118).

Common situations: Implementing ThrowsAdvice without defining any afterThrowing method (the interface itself declares no methods, so the compiler won't help). Typos like 'afterthrowing' (lowercase) or 'onThrowing'. Declaring the handler private or with a non-Throwable sole parameter. Migrating from a custom exception-handling advice that used a different method name.

Related errors


AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09). Data as JSON: /api/errors/c8c05ac1d9d2a6e5. Report an issue: GitHub.

Appendix: source

Thrown at spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java:119

				}
				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.
	 */
	public int getHandlerMethodCount() {
		return this.exceptionHandlerMap.size();
	}


	@Override
	public @Nullable Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}

View on GitHub (pinned to 69bf83ad71)