{"record":{"id":"a3898913436f8577","repo":"spring-projects/spring-ai","slug":"multiple-tools-with-the-same-name-s-found-in-so","errorCode":null,"errorMessage":"Multiple tools with the same name (%s) found in sources: %s","messagePattern":"Multiple tools with the same name \\((.+?)\\) found in sources: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java","lineNumber":133,"sourceCode":"\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(\", \"))));\n\t\t}\n\t}\n\n\tpublic static Builder builder() {\n\t\treturn new Builder();\n\t}\n\n\tpublic static final class Builder {\n\n\t\tprivate List<Object> toolObjects = new ArrayList<>();\n\n\t\tprivate Builder() {\n\t\t}\n\n\t\tpublic Builder toolObjects(Object... toolObjects) {\n\t\t\tAssert.notNull(toolObjects, \"toolObjects cannot be null\");","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java#L115-L151","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n@Tool public String lookup(String id) {...} // in OrdersTools\n@Tool public String lookup(String id) {...} // in InventoryTools -> duplicate 'lookup'\n// after\n@Tool(name = \"orders_lookup\") public String lookup(String id) {...} // OrdersTools\n@Tool(name = \"inventory_lookup\") public String lookup(String id) {...} // InventoryTools","handlingStrategy":"validation","validationCode":"Map<String, Long> counts = Arrays.stream(toolCallbacks)\n    .collect(Collectors.groupingBy(cb -> cb.getToolDefinition().name(), Collectors.counting()));\nList<String> dupes = counts.entrySet().stream().filter(e -> e.getValue() > 1).map(Map.Entry::getKey).toList();\nif (!dupes.isEmpty()) throw new IllegalArgumentException(\"Duplicate tool names: \" + dupes);","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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."],"tags":["naming-conflict","tool-registration","spring-ai"],"backgroundTag":"conflicting-config-options","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}