{"record":{"id":"56779169cab51cd9","repo":"spring-projects/spring-ai","slug":"multiple-tools-with-the-same-name-s-found-in-to","errorCode":null,"errorMessage":"Multiple tools with the same name (%s) found in ToolCallingChatOptions","messagePattern":"Multiple tools with the same name \\((.+?)\\) found in ToolCallingChatOptions","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java","lineNumber":98,"sourceCode":"\t\t\treturn defaultToolContext != null ? Map.copyOf(defaultToolContext) : null;\n\t\t}\n\t\tAssert.noNullElements(runtimeToolContext.keySet(), \"runtimeToolContext keys cannot be null\");\n\t\tif (CollectionUtils.isEmpty(defaultToolContext)) {\n\t\t\treturn Map.copyOf(runtimeToolContext);\n\t\t}\n\t\tAssert.noNullElements(defaultToolContext.keySet(), \"defaultToolContext keys cannot be null\");\n\t\tvar mergedToolContext = new java.util.HashMap<>(defaultToolContext);\n\t\tmergedToolContext.putAll(runtimeToolContext);\n\t\treturn Map.copyOf(mergedToolContext);\n\t}\n\n\tstatic void validateToolCallbacks(@Nullable List<ToolCallback> toolCallbacks) {\n\t\tif (CollectionUtils.isEmpty(toolCallbacks)) {\n\t\t\treturn;\n\t\t}\n\t\tList<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);\n\t\tif (!duplicateToolNames.isEmpty()) {\n\t\t\tthrow new IllegalStateException(\"Multiple tools with the same name (%s) found in ToolCallingChatOptions\"\n\t\t\t\t.formatted(String.join(\", \", duplicateToolNames)));\n\t\t}\n\t}\n\n\t/**\n\t * A builder to create a {@link ToolCallingChatOptions} instance.\n\t */\n\tinterface Builder<B extends Builder<B>> extends ChatOptions.Builder<B> {\n\n\t\t/**\n\t\t * ToolCallbacks to be registered with the ChatModel.\n\t\t */\n\t\tB toolCallbacks(@Nullable List<ToolCallback> toolCallbacks);\n\n\t\t/**\n\t\t * ToolCallbacks to be registered with the ChatModel.\n\t\t */\n\t\tB toolCallbacks(ToolCallback... toolCallbacks);","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java#L80-L116","documentation":"ToolCallingChatOptions.validateToolCallbacks rejects a tool callback list containing two or more callbacks with the same tool name, since the model and manager could not disambiguate which implementation to invoke. It uses ToolUtils.getDuplicateToolNames and throws IllegalStateException listing the duplicated names.","triggerScenarios":"Building ToolCallingChatOptions (or calling a ChatModel with them) where getToolCallbacks() contains callbacks with identical names, e.g. after merging tool lists from multiple ToolCallbackProviders or registering the same @Tool method twice.","commonSituations":"Combining tool sources (MCP client + local tools) that both expose a tool named e.g. 'search', accidentally registering the same FunctionCallback twice, or version migrations registering defaults plus explicit duplicates.","solutions":["Deduplicate the callback list before setting it (filter by ToolCallback.getName())","Rename one of the conflicting tools (unique @Tool name / FunctionCallback name)","Inspect which providers contributed duplicates (log each callback name before building options)","If both tools are wanted, wrap or prefix names so each is unique"],"exampleFix":"// before\noptions.setToolCallbacks(List.of(providerA.getToolCallbacks(), providerB.getToolCallbacks()).stream().flatMap(List::stream).toList());\n// after\nSet<String> seen = new HashSet<>();\nList<ToolCallback> unique = all.stream().filter(cb -> seen.add(cb.getName())).toList();\noptions.setToolCallbacks(unique);","handlingStrategy":"validation","validationCode":"List<ToolCallback> cbs = options.getToolCallbacks();\nSet<String> names = new HashSet<>();\nfor (ToolCallback cb : cbs) {\n    if (!names.add(cb.getName())) {\n        throw new IllegalStateException(\"Duplicate tool name: \" + cb.getName());\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Deduplicate callbacks whenever merging multiple ToolCallbackProviders","Namespace tool names per provider (e.g. mcp_search vs local_search)","Unit-test the assembled options for duplicate names before hitting the model"],"tags":["spring-ai","tool-calling","duplicate-names","configuration"],"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"}