spring-projects/spring-ai · error · IllegalStateException
At most one ToolAdvisor is allowed in the advisor chain, but
Error message
At most one ToolAdvisor is allowed in the advisor chain, but found
What it means
DefaultChatClient validates during build (validateSingleToolAdvisor) that the advisor chain contains at most one ToolAdvisor. Having two or more would apply tool-calling advice twice, so it fails fast with an IllegalStateException listing the duplicate advisors' names and order values.
Source
Thrown at spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java:1245
return;
}
int configuredOrder = this.toolCallingAdvisorBuilder.getAdvisorOrder();
boolean hasDownstreamMemoryAdvisor = this.advisors.stream()
.anyMatch(a -> a instanceof MemoryAdvisor && a.getOrder() > configuredOrder);
this.advisors.add(this.toolCallingAdvisorBuilder.copy()
.conversationHistoryEnabled(!hasDownstreamMemoryAdvisor)
.build());
}
private void validateSingleToolAdvisor() {
List<Advisor> toolAdvisors = this.advisors.stream().filter(a -> a instanceof ToolAdvisor).toList();
if (toolAdvisors.size() > 1) {
String names = String.join(", ",
toolAdvisors.stream().map(a -> a.getName() + " (order=" + a.getOrder() + ")").toList());
throw new IllegalStateException("At most one ToolAdvisor is allowed in the advisor chain, but found "
+ toolAdvisors.size() + ": [" + names + "]");
}
}
}
}
View on GitHub (pinned to 98a7beda4f)
Solutions
- Search your advisor configuration and register only one ToolAdvisor per ChatClient.
- If both advisors are needed, merge their tool-calling configuration into a single ToolAdvisor.
- Inspect the exception message's [names (order=...)] list to locate the exact duplicate registrations.
Example fix
// before .defaultAdvisors(ToolAdvisor.builder().build(), ToolAdvisor.builder().build()) // after .defaultAdvisors(ToolAdvisor.builder().build())
Defensive patterns
Strategy: validation
Validate before calling
List<Advisor> toolAdvisors = advisorList.stream().filter(a -> a instanceof ToolAdvisor).toList();
if (toolAdvisors.size() > 1) throw new IllegalStateException("only one ToolAdvisor allowed, got: " + toolAdvisors); Try / catch
try { chatClient = builder.build(); } catch (IllegalStateException e) { if (e.getMessage().contains("ToolAdvisor")) { /* dedupe advisor config */ } throw e; } Prevention
- Centralize advisor registration in one config class to avoid duplicates.
- Audit default vs per-request advisor lists for overlapping ToolAdvisor additions.
- Read the exception's [name (order=...)] list to find the duplicate registration points.
When it happens
Trigger: Calling .advisors(...) / .defaultAdvisors(...) with two instances of ToolAdvisor (or two ToolCallingManager-style advisors) on the same ChatClient before build().
Common situations: Copy-pasted advisor configuration, merging default advisors with per-request advisors where both add a ToolAdvisor, or upgrading/migrating code where a ToolAdvisor was added in two config places.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- No CallAdvisors available to execute
- The specified advisor is not part of the chain:
- Only outputType or outputJsonSchema can be set, not both.
- mutate() must be overridden to return the most concrete Buil
- Multiple tools with the same name (%s) found in ToolCallingC
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/a7ea3b0cdb77e159.
Report an issue: GitHub.