{"record":{"id":"0c20fbb3c65ac962","repo":"spring-projects/spring-ai","slug":"method-toolmethod-getname-is-annotated-with","errorCode":null,"errorMessage":"Method ${toolMethod.getName()} is annotated with @Tool but returns a functional type. This is not supported and the method will be ignored.","messagePattern":"Method (.+?) is annotated with @Tool but returns a functional type\\. This is not supported and the method will be ignored\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java","lineNumber":117,"sourceCode":"\t\t\t\t\t.toolCallResultConverter(ToolUtils.getToolCallResultConverter(toolMethod))\n\t\t\t\t\t.build())\n\t\t\t\t.toArray(ToolCallback[]::new))\n\t\t\t.flatMap(Stream::of)\n\t\t\t.toArray(ToolCallback[]::new);\n\n\t\tvalidateToolCallbacks(toolCallbacks);\n\n\t\treturn toolCallbacks;\n\t}\n\n\tprivate boolean isFunctionalType(Method toolMethod) {\n\t\tvar isFunction = ClassUtils.isAssignable(Function.class, toolMethod.getReturnType())\n\t\t\t\t|| ClassUtils.isAssignable(Supplier.class, toolMethod.getReturnType())\n\t\t\t\t|| ClassUtils.isAssignable(Consumer.class, toolMethod.getReturnType());\n\n\t\tif (isFunction) {\n\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\tlogger.warn(\"Method \" + toolMethod.getName() + \"is annotated with @Tool but returns a functional type. \"\n\t\t\t\t\t\t+ \"This is not supported and the method will be ignored.\");\n\t\t\t}\n\t\t}\n\n\t\treturn isFunction;\n\t}\n\n\tprivate boolean isToolAnnotatedMethod(Method method) {\n\t\tTool annotation = AnnotationUtils.findAnnotation(method, Tool.class);\n\t\treturn Objects.nonNull(annotation);\n\t}\n\n\tprivate void validateToolCallbacks(ToolCallback[] toolCallbacks) {\n\t\tList<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);\n\t\tif (!duplicateToolNames.isEmpty()) {\n\t\t\tthrow new IllegalArgumentException(\"Multiple tools with the same name (%s) found in sources: %s\".formatted(\n\t\t\t\t\tString.join(\", \", duplicateToolNames),\n\t\t\t\t\tthis.toolObjects.stream().map(o -> o.getClass().getName()).collect(Collectors.joining(\", \"))));","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java#L99-L135","documentation":"MethodToolCallbackProvider skips @Tool-annotated methods whose return type is a functional type (Function, Supplier, Consumer), logging this warning. The Spring AI tool API expects tool methods to return plain data; returning a lambda/functional interface is unsupported, so the method is ignored and will not become a ToolCallback.","triggerScenarios":"Building tool callbacks from a class (e.g. ToolCallbacks.from(obj) or bean-based scanning) where a @Tool method returns Function/Supplier/Consumer; detected in isFunctionalType, consulted by toolMethods and toolCallbacks.","commonSituations":"Refactoring a Function-based (legacy FunctionCallback) tool into a @Tool method without changing the return type; wrapping logic in a Supplier for laziness; migration from old tool API styles.","solutions":["Change the @Tool method to return a concrete data type and execute the function inside the method body.","If you need function-style tools, keep using the FunctionCallback/ToolCallback API instead of @Tool.","Split the code: keep the lambda private, expose a @Tool method that calls it and returns the result."],"exampleFix":"// before\n@Tool(description=\"Get weather\")\nFunction<String, String> getWeather() {\n    return city -> weatherService.fetch(city);\n}\n\n// after\n@Tool(description=\"Get weather\")\nString getWeather(String city) {\n    return weatherService.fetch(city);\n}","handlingStrategy":"validation","validationCode":"for (Method m : clazz.getDeclaredMethods()) {\n    if (m.isAnnotationPresent(Tool.class) &&\n        (Function.class.isAssignableFrom(m.getReturnType()) ||\n         Supplier.class.isAssignableFrom(m.getReturnType()) ||\n         Consumer.class.isAssignableFrom(m.getReturnType()))) {\n        throw new IllegalStateException(\"@Tool method returns functional type: \" + m.getName());\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["@Tool methods must return plain data types, never Function/Supplier/Consumer.","When migrating legacy FunctionCallback tools, inline the function logic into the method body.","Scan your tool classes in CI to catch functional return types early.","Call toolCallbacks on the provider in a startup test so skipped methods surface as count mismatches."],"tags":["tool-calling","unsupported","annotations"],"backgroundTag":"unsupported-operation","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}