{"record":{"id":"838c2234531a705a","repo":"spring-projects/spring-ai","slug":"cannot-set-both-delegate-and-toolobject","errorCode":null,"errorMessage":"Cannot set both delegate and toolObject","messagePattern":"Cannot set both delegate and toolObject","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"spring-ai-model/src/main/java/org/springframework/ai/tool/augment/AugmentedToolCallbackProvider.java","lineNumber":154,"sourceCode":"\t\t\tthis.removeExtraArgumentsAfterProcessing = removeExtraArgumentsAfterProcessing;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Builds the {@link AugmentedToolCallbackProvider} instance.\n\t\t * @return the built instance\n\t\t * @throws IllegalStateException if required fields are not set\n\t\t */\n\t\tpublic AugmentedToolCallbackProvider<T> build() {\n\t\t\tif (this.argumentType == null) {\n\t\t\t\tthrow new IllegalStateException(\"argumentType is required\");\n\t\t\t}\n\t\t\tif (this.argumentConsumer == null) {\n\t\t\t\tthrow new IllegalStateException(\"argumentConsumer is required\");\n\t\t\t}\n\n\t\t\tif (this.delegate != null && this.toolObject != null) {\n\t\t\t\tthrow new IllegalStateException(\"Cannot set both delegate and toolObject\");\n\t\t\t}\n\n\t\t\tif (this.delegate == null && this.toolObject == null) {\n\t\t\t\tthrow new IllegalStateException(\"Either delegate or toolObject must be set\");\n\t\t\t}\n\n\t\t\tif (this.toolObject != null) {\n\t\t\t\treturn new AugmentedToolCallbackProvider<>(this.toolObject, this.argumentType, this.argumentConsumer,\n\t\t\t\t\t\tthis.removeExtraArgumentsAfterProcessing);\n\t\t\t}\n\t\t\telse if (this.delegate != null) { // Redundant if condition to please NullAway\n\t\t\t\treturn new AugmentedToolCallbackProvider<>(this.delegate, this.argumentType, this.argumentConsumer,\n\t\t\t\t\t\tthis.removeExtraArgumentsAfterProcessing);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new IllegalStateException();\n\t\t\t}\n\t\t}","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-model/src/main/java/org/springframework/ai/tool/augment/AugmentedToolCallbackProvider.java#L136-L172","documentation":"build() enforces that delegate (a ToolCallback/ToolCallbackProvider delegate) and toolObject (an object with @Tool methods) are mutually exclusive sources for the provider. Setting both would create ambiguity about which source provides the tools, so the builder rejects it with an IllegalStateException.","triggerScenarios":"Calling builder().delegate(delegateProvider).toolObject(myToolObject)...build(); typically when migrating code from the toolObject style to the delegate style and forgetting to remove the old setter.","commonSituations":"Refactoring from @Tool-annotated objects to wrapping existing ToolCallbacks; copy-pasted builder chains where both setters appear; fluent chains reused across two provider configurations.","solutions":["Remove either .delegate(...) or .toolObject(...) from the builder chain, keeping only the intended source.","If you need to combine both kinds of tools, create two AugmentedToolCallbackProvider instances and merge their ToolCallbacks.","Audit shared builder helper methods so they only set one source."],"exampleFix":"// before\nvar provider = AugmentedToolCallbackProvider.builder()\n    .delegate(wrappedCallbacks)\n    .toolObject(new MyTools())\n    .argumentType(Args.class)\n    .argumentConsumer(a -> a)\n    .build();\n// after\nvar provider = AugmentedToolCallbackProvider.builder()\n    .toolObject(new MyTools())\n    .argumentType(Args.class)\n    .argumentConsumer(a -> a)\n    .build();","handlingStrategy":"validation","validationCode":"boolean hasDelegate = delegate != null;\nboolean hasToolObject = toolObject != null;\nif (hasDelegate == hasToolObject && !hasDelegate) { /* neither */ }\nif (hasDelegate && hasToolObject) {\n    throw new IllegalArgumentException(\"Set only one of delegate or toolObject\");\n}","typeGuard":null,"tryCatchPattern":"try { provider = builder.build(); } catch (IllegalStateException e) { throw new IllegalArgumentException(\"Invalid tool source configuration: \" + e.getMessage(), e); }","preventionTips":["Pick one tool-source style (toolObject vs delegate) per codebase and enforce it in review.","When migrating between styles, delete the old setter call in the same commit."],"tags":["builder-validation","mutually-exclusive","spring-ai"],"backgroundTag":"mutually-exclusive-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"}