{"record":{"id":"aa9570a3deaac49f","repo":"spring-projects/spring-ai","slug":"undeclaredthrowableexception","errorCode":null,"errorMessage":"UndeclaredThrowableException","messagePattern":"UndeclaredThrowableException","errorType":"exception","errorClass":"UndeclaredThrowableException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/tool/AbstractMcpToolMethodCallback.java","lineNumber":90,"sourceCode":"\t * @throws RuntimeException if there's an error invoking the method\n\t */\n\tprotected Object callMethod(Object[] methodArguments) {\n\t\tthis.toolMethod.setAccessible(true);\n\t\ttry {\n\t\t\treturn this.toolMethod.invoke(this.toolObject, methodArguments);\n\t\t}\n\t\tcatch (IllegalAccessException ex) {\n\t\t\tthrow new RuntimeException(\"Failed to access tool method\", ex);\n\t\t}\n\t\tcatch (InvocationTargetException ex) {\n\t\t\tThrowable cause = ex.getCause();\n\t\t\tif (cause instanceof RuntimeException re) {\n\t\t\t\tthrow re;\n\t\t\t}\n\t\t\tif (cause instanceof Error err) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tthrow new UndeclaredThrowableException(cause);\n\t\t}\n\t}\n\n\t/**\n\t * Builds the method arguments from the context, tool input arguments, and optionally\n\t * the full request.\n\t * @param exchangeOrContext The exchange or context object (e.g.,\n\t * McpSyncServerExchange, McpAsyncServerExchange, or McpTransportContext)\n\t * @param toolInputArguments The input arguments from the tool request\n\t * @param request The full CallToolRequest (optional, can be null)\n\t * @return An array of method arguments\n\t */\n\tprotected Object[] buildMethodArguments(T exchangeOrContext, Map<String, Object> toolInputArguments,\n\t\t\tCallToolRequest request) {\n\n\t\treturn Stream.of(this.toolMethod.getParameters()).map(parameter -> {\n\n\t\t\tif (McpSyncRequestContext.class.isAssignableFrom(parameter.getType())","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/tool/AbstractMcpToolMethodCallback.java#L72-L108","documentation":"AbstractMcpToolMethodCallback.callMethod rethrows the checked exception raised inside a tool method as UndeclaredThrowableException when it is neither a RuntimeException nor an Error. Because the callback's invoke path does not declare checked exceptions, any checked exception thrown by the user's tool method is wrapped; the real cause is available via getCause().","triggerScenarios":"A tool method declares and throws a checked exception (e.g. IOException, SQLException) and the underlying transport call hits that path.","commonSituations":"Tool methods calling file/DB/network APIs with checked exceptions; proxy-based invocation (proxies only propagate unchecked exceptions) making the wrap mandatory.","solutions":["Unwrap with exception.getCause() in the calling error handler to see the real checked exception","Change the tool method to wrap checked exceptions in a RuntimeException (or a custom unchecked type)","Catch the checked exception inside the tool method and return an error CallToolResult instead of throwing","Catch UndeclaredThrowableException at the dispatch boundary and inspect its cause"],"exampleFix":"// before\nString readFile(String p) throws IOException { return Files.readString(Path.of(p)); }\n// after\nString readFile(String p) {\n    try { return Files.readString(Path.of(p)); }\n    catch (IOException e) { throw new UncheckedIOException(e); }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    result = callback.call(request);\n} catch (UndeclaredThrowableException e) {\n    Throwable real = e.getCause();\n    log.error(\"Tool method threw checked exception\", real);\n    return errorCallToolResult(real);\n}","preventionTips":["Wrap checked exceptions in unchecked types inside tool methods","Catch checked exceptions in the tool and convert to error CallToolResult","Never let raw checked exceptions escape tool method signatures"],"tags":["checked-exception","reflection","tool"],"backgroundTag":"undeclared-checked-exception","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}