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
- Add a public 'void afterThrowing(Exception ex)' (or any Throwable subclass) method to your ThrowsAdvice implementation.
- If you need full context, use the four-arg form: 'void afterThrowing(Method method, Object[] args, Object target, Exception ex)'.
- Verify the method name is exactly 'afterThrowing' and the parameter types match the supported signatures (single Throwable, or Method/Object[]/Object/Throwable).
- 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
- Define at least one public void afterThrowing(Exception) method when implementing ThrowsAdvice.
- Treat ThrowsAdvice as documentation-only and add a unit test asserting getHandlerMethodCount > 0.
- Avoid private or mis-spelled afterThrowing methods.
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
- Invalid afterThrowing signature: four arguments must be…
- Invalid afterThrowing signature: single argument must be a…
- Only one afterThrowing method per specific Throwable…
- Unsupported afterThrowing signature: single throwable…
- Advice must be declared inside an aspect type: Offending…
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)