spring-projects/spring-ai · error · IllegalStateException
Cannot set both delegate and toolObject
Error message
Cannot set both delegate and toolObject
What it means
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.
Source
Thrown at spring-ai-model/src/main/java/org/springframework/ai/tool/augment/AugmentedToolCallbackProvider.java:154
this.removeExtraArgumentsAfterProcessing = removeExtraArgumentsAfterProcessing;
return this;
}
/**
* Builds the {@link AugmentedToolCallbackProvider} instance.
* @return the built instance
* @throws IllegalStateException if required fields are not set
*/
public AugmentedToolCallbackProvider<T> build() {
if (this.argumentType == null) {
throw new IllegalStateException("argumentType is required");
}
if (this.argumentConsumer == null) {
throw new IllegalStateException("argumentConsumer is required");
}
if (this.delegate != null && this.toolObject != null) {
throw new IllegalStateException("Cannot set both delegate and toolObject");
}
if (this.delegate == null && this.toolObject == null) {
throw new IllegalStateException("Either delegate or toolObject must be set");
}
if (this.toolObject != null) {
return new AugmentedToolCallbackProvider<>(this.toolObject, this.argumentType, this.argumentConsumer,
this.removeExtraArgumentsAfterProcessing);
}
else if (this.delegate != null) { // Redundant if condition to please NullAway
return new AugmentedToolCallbackProvider<>(this.delegate, this.argumentType, this.argumentConsumer,
this.removeExtraArgumentsAfterProcessing);
}
else {
throw new IllegalStateException();
}
}View on GitHub (pinned to 98a7beda4f)
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.
Example fix
// before
var provider = AugmentedToolCallbackProvider.builder()
.delegate(wrappedCallbacks)
.toolObject(new MyTools())
.argumentType(Args.class)
.argumentConsumer(a -> a)
.build();
// after
var provider = AugmentedToolCallbackProvider.builder()
.toolObject(new MyTools())
.argumentType(Args.class)
.argumentConsumer(a -> a)
.build(); Defensive patterns
Strategy: validation
Validate before calling
boolean hasDelegate = delegate != null;
boolean hasToolObject = toolObject != null;
if (hasDelegate == hasToolObject && !hasDelegate) { /* neither */ }
if (hasDelegate && hasToolObject) {
throw new IllegalArgumentException("Set only one of delegate or toolObject");
} Try / catch
try { provider = builder.build(); } catch (IllegalStateException e) { throw new IllegalArgumentException("Invalid tool source configuration: " + e.getMessage(), e); } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Refactoring from @Tool-annotated objects to wrapping existing ToolCallbacks; copy-pasted builder chains where both setters appear; fluent chains reused across two provider configurations.
Related errors
- argumentConsumer is required
- Either delegate or toolObject must be set
- Bean must not be null
- Method must not be null
- Bean must not be null
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/838c2234531a705a.
Report an issue: GitHub.