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

  1. Give each @Tool method a unique explicit name: @Tool(name = "orders_lookup") instead of relying on the method name.
  2. Remove duplicate registrations of the same toolObject from the builder.
  3. 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

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


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/a3898913436f8577. Report an issue: GitHub.