spring-projects/spring-ai · error · IllegalArgumentException
No @Tool annotated methods found in %s. Did you mean to pass
Error message
No @Tool annotated methods found in %s. Did you mean to pass a ToolCallback or ToolCallbackProvider? If so, use .tools(toolCallback) or .toolCallbacks(toolCallback) instead.
What it means
MethodToolCallbackProvider scans each supplied toolObject for @Tool-annotated methods (skipping functional types). If an object yields zero @Tool methods, it throws an IllegalArgumentException explaining that a ToolCallback/ToolCallbackProvider should be passed with .tools()/.toolCallbacks() instead. It fails fast because a provider over an object with no tools is almost certainly a misuse of the API.
Source
Thrown at spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java:78
Assert.notNull(toolObjects, "toolObjects cannot be null");
Assert.noNullElements(toolObjects, "toolObjects cannot contain null elements");
assertToolAnnotatedMethodsPresent(toolObjects);
this.toolObjects = toolObjects;
validateToolCallbacks(getToolCallbacks());
}
private void assertToolAnnotatedMethodsPresent(List<Object> toolObjects) {
for (Object toolObject : toolObjects) {
List<Method> toolMethods = Stream
.of(ReflectionUtils.getDeclaredMethods(
AopUtils.isAopProxy(toolObject) ? AopUtils.getTargetClass(toolObject) : toolObject.getClass()))
.filter(this::isToolAnnotatedMethod)
.filter(toolMethod -> !isFunctionalType(toolMethod))
.toList();
if (toolMethods.isEmpty()) {
throw new IllegalArgumentException("No @Tool annotated methods found in " + toolObject + ". "
+ "Did you mean to pass a ToolCallback or ToolCallbackProvider? If so, use"
+ " .tools(toolCallback) or .toolCallbacks(toolCallback) instead.");
}
}
}
@Override
public ToolCallback[] getToolCallbacks() {
var toolCallbacks = this.toolObjects.stream()
.map(toolObject -> Stream
.of(ReflectionUtils.getDeclaredMethods(
AopUtils.isAopProxy(toolObject) ? AopUtils.getTargetClass(toolObject) : toolObject.getClass()))
.filter(this::isToolAnnotatedMethod)
.filter(toolMethod -> !isFunctionalType(toolMethod))
.filter(ReflectionUtils.USER_DECLARED_METHODS::matches)
.map(toolMethod -> MethodToolCallback.builder()
.toolDefinition(ToolDefinitions.from(toolMethod))
.toolMetadata(ToolMetadata.from(toolMethod))View on GitHub (pinned to 98a7beda4f)
Solutions
- Annotate at least one public method of the object with org.springframework.ai.tool.annotation.@Tool.
- If you already have ToolCallbacks, use .tools(toolCallback) or .toolCallbacks(provider) instead of .toolObject(...).
- Verify the @Tool import resolves to Spring AI's annotation, not another library's.
Example fix
// before MethodToolCallbackProvider.builder().toolObject(new MyCallbackProvider()).build(); // no @Tool methods // after ChatClient.builder().defaultToolCallbacks(myCallbackProvider); // or annotate methods in the object with @Tool
Defensive patterns
Strategy: validation
Validate before calling
long toolCount = Arrays.stream(obj.getClass().getMethods())
.filter(m -> m.isAnnotationPresent(org.springframework.ai.tool.annotation.Tool.class))
.count();
if (toolCount == 0) throw new IllegalArgumentException(obj + " has no @Tool methods; use toolCallbacks instead"); Type guard
boolean isToolObject = Arrays.stream(o.getClass().getMethods()).anyMatch(m -> m.isAnnotationPresent(Tool.class));
Try / catch
try { provider = MethodToolCallbackProvider.builder().toolObject(obj).build(); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("No @Tool annotated methods")) throw new InvalidToolSourceException(obj, e); throw e; } Prevention
- Check @Tool imports resolve to org.springframework.ai.tool.annotation.Tool.
- Register ToolCallbacks via .tools()/.toolCallbacks(), objects with @Tool methods via .toolObject() — never cross them.
- Cover provider construction in a startup test.
When it happens
Trigger: Passing a ToolCallback, ToolCallbackProvider, or plain object without any @Tool methods into MethodToolCallbackProvider.builder().toolObject(...); wrong imports of @Tool (not org.springframework.ai.tool.annotation.Tool).
Common situations: Migrating code where callbacks were previously registered via .tools() and someone switched to toolObject(); importing a custom/other framework's @Tool annotation; Lombok/AOP hiding annotations from scanning.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- This is a utility class and cannot be instantiated
- Multiple tools with the same name (%s) found in sources: %s
- Method cannot have more than one @McpProgressToken parameter
- Method cannot have more than one McpMeta parameter: {method.
- Method cannot have more than one request context parameter:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/d701081ce1055b84.
Report an issue: GitHub.