spring-projects/spring-ai · error · IllegalArgumentException
Multiple tools with the same name
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.
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.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- No @Tool annotated methods found in
- Anthropic cache breakpoint limit (4) reached. Additional…
- Anthropic Citations API requires all documents to have…
- argumentConsumer is required
- argumentType is required
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/a3898913436f8577.
Report an issue: GitHub.
Appendix: 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)