spring-projects/spring-ai · error · IllegalArgumentException
Multiple tools with the same name (%s) found in sources: %s
Error message
Multiple tools with the same name (%s) found in sources: %s
What it means
MethodToolCallbackProvider.validateToolCallbacks() checks the assembled ToolCallback array for duplicate tool names using ToolUtils.getDuplicateToolNames. Tool names must be unique for the model to address them, so duplicates cause an IllegalArgumentException listing the conflicting names and the source classes.
Source
Thrown at spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java:133
if (isFunction) {
if (logger.isWarnEnabled()) {
logger.warn("Method " + toolMethod.getName() + "is annotated with @Tool but returns a functional type. "
+ "This is not supported and the method will be ignored.");
}
}
return isFunction;
}
private boolean isToolAnnotatedMethod(Method method) {
Tool annotation = AnnotationUtils.findAnnotation(method, Tool.class);
return Objects.nonNull(annotation);
}
private void validateToolCallbacks(ToolCallback[] toolCallbacks) {
List<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);
if (!duplicateToolNames.isEmpty()) {
throw new IllegalArgumentException("Multiple tools with the same name (%s) found in sources: %s".formatted(
String.join(", ", duplicateToolNames),
this.toolObjects.stream().map(o -> o.getClass().getName()).collect(Collectors.joining(", "))));
}
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private List<Object> toolObjects = new ArrayList<>();
private Builder() {
}
public Builder toolObjects(Object... toolObjects) {
Assert.notNull(toolObjects, "toolObjects cannot be null");View on GitHub (pinned to 98a7beda4f)
Solutions
- Give each @Tool method a unique explicit name: @Tool(name = "orders_lookup") instead of relying on the method name.
- Remove duplicate registrations of the same toolObject from the builder.
- Check the listed duplicate names and source classes in the message to locate the colliding methods.
Example fix
// before
@Tool public String lookup(String id) {...} // in OrdersTools
@Tool public String lookup(String id) {...} // in InventoryTools -> duplicate 'lookup'
// after
@Tool(name = "orders_lookup") public String lookup(String id) {...} // OrdersTools
@Tool(name = "inventory_lookup") public String lookup(String id) {...} // InventoryTools Defensive patterns
Strategy: validation
Validate before calling
Map<String, Long> counts = Arrays.stream(toolCallbacks)
.collect(Collectors.groupingBy(cb -> cb.getToolDefinition().name(), Collectors.counting()));
List<String> dupes = counts.entrySet().stream().filter(e -> e.getValue() > 1).map(Map.Entry::getKey).toList();
if (!dupes.isEmpty()) throw new IllegalArgumentException("Duplicate tool names: " + dupes); Try / catch
try { provider = MethodToolCallbackProvider.builder().toolObjects(tools).build(); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("Multiple tools with the same name")) throw new DuplicateToolNameException(e); throw e; } Prevention
- Always set explicit unique @Tool(name = "...") names prefixed by domain (e.g. orders_get).
- Register each toolObject only once per client.
- Lint tool name uniqueness in a unit test over the full tool registry.
When it happens
Trigger: Two toolObjects each defining a @Tool method with the same name (default name = method name); the same toolObject registered twice; overlapping default names like 'execute' across unrelated tool classes.
Common situations: Multiple tool classes with identically named methods registered in one ChatClient; refactoring that moved a @Tool method into a second class without renaming; combining several providers into one tool registry.
Related errors
- No @Tool annotated methods found in %s. Did you mean to pass
- Async complete methods should use McpAsyncRequestContext ins
- Method cannot have more than one McpMeta parameter: ${method
- Method cannot have more than one exchange parameter: ${metho
- Method cannot have more than one ReadResourceRequest or Stri
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/a3898913436f8577.
Report an issue: GitHub.