{"record":{"id":"c8c05ac1d9d2a6e5","repo":"spring-projects/spring-framework","slug":"at-least-one-handler-method-must-be-found-in-class","errorCode":null,"errorMessage":"At least one handler method must be found in class [{}]","messagePattern":"At least one handler method must be found in class \\[(.+?)\\]","errorType":"exception","errorClass":"AopConfigException","httpStatus":null,"severity":"error","filePath":"spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java","lineNumber":119,"sourceCode":"\t\t\t\t}\n\t\t\t\tif (throwableParam == null) {\n\t\t\t\t\tthrow new AopConfigException(\"Unsupported afterThrowing signature: single throwable argument \" +\n\t\t\t\t\t\t\t\"or four arguments Method, Object[], target, throwable expected: \" + method);\n\t\t\t\t}\n\t\t\t\t// An exception handler to register...\n\t\t\t\tMethod existingMethod = this.exceptionHandlerMap.put(throwableParam, method);\n\t\t\t\tif (existingMethod != null) {\n\t\t\t\t\tthrow new AopConfigException(\"Only one afterThrowing method per specific Throwable subclass \" +\n\t\t\t\t\t\t\t\"allowed: \" + method + \" / \" + existingMethod);\n\t\t\t\t}\n\t\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\t\tlogger.debug(\"Found exception handler method on throws advice: \" + method);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.exceptionHandlerMap.isEmpty()) {\n\t\t\tthrow new AopConfigException(\n\t\t\t\t\t\"At least one handler method must be found in class [\" + throwsAdvice.getClass() + \"]\");\n\t\t}\n\t}\n\n\n\t/**\n\t * Return the number of handler methods in this advice.\n\t */\n\tpublic int getHandlerMethodCount() {\n\t\treturn this.exceptionHandlerMap.size();\n\t}\n\n\n\t@Override\n\tpublic @Nullable Object invoke(MethodInvocation mi) throws Throwable {\n\t\ttry {\n\t\t\treturn mi.proceed();\n\t\t}","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/spring-projects/spring-framework/blob/69bf83ad716d0cfc4b0520a19b4d8b24c79d1538/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java#L101-L137","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\npublic class MyThrowsAdvice implements ThrowsAdvice {\n    // no methods defined\n}\n\n// after\npublic class MyThrowsAdvice implements ThrowsAdvice {\n    public void afterThrowing(Exception ex) {\n        // handle exception\n    }\n}","handlingStrategy":"validation","validationCode":"// Verify a ThrowsAdvice implementation has at least one valid handler before wiring\npublic static boolean hasValidAfterThrowingHandler(Object advice) {\n    for (Method m : advice.getClass().getMethods()) {\n        if (!\"afterThrowing\".equals(m.getName())) continue;\n        Class<?>[] p = m.getParameterTypes();\n        if (p.length == 1 && Throwable.class.isAssignableFrom(p[0])) return true;\n        if (p.length == 4 && Method.class == p[0] && Object[].class == p[1]\n                && Throwable.class.isAssignableFrom(p[3])) return true;\n    }\n    return false;\n}","typeGuard":"// ThrowsAdvice is a marker interface; guard on method shape instead\nstatic boolean isValidThrowsAdvice(Object o) {\n    return o != null && hasValidAfterThrowingHandler(o);\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["spring-aop","throws-advice","configuration","reflection"],"backgroundTag":null,"analyzedSha":"69bf83ad716d0cfc4b0520a19b4d8b24c79d1538","analyzedAt":"2026-08-09T15:32:58.770Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}