A Guide to Using Recursive Advisors in Spring AI
AI applications often require iterative interactions with language models to refine outputs, execute tools, or validate responses. Instead of manually managing these loops in application code, Spring AI introduces Recursive Advisors, which extend the advisor chain to support controlled, repeatable execution of model calls based on defined conditions.
Recursive advisors enable patterns such as retries, structured output validation, and tool-driven workflows by re-invoking downstream advisors until a termination condition is satisfied. This approach simplifies the design of robust, production-ready AI pipelines within Spring applications. In this article, we explore how to configure and implement recursive advisors using Spring AI
1. Understanding Advisors in Spring AI
Spring AI provides an Advisor API that allows us to intercept and modify AI interactions before and after the model execution. Advisors operate in a chain similar to filters or interceptors.
Each advisor receives a request, optionally modifies it, and then passes control to the next advisor in the chain before eventually reaching the model. When the response returns, the chain is executed in reverse order, so advisors can also process the response.
1.1 What Are Recursive Advisors?
A standard advisor executes once during the request lifecycle. A Recursive Advisor, however, can repeatedly invoke the downstream advisor chain until a specific condition is satisfied. This enables us to implement iterative AI workflows, including tool execution loops, structured output validation with retries, LLM response evaluation, and agent-like reasoning loops.
Recursive advisors work by creating a copy of the downstream advisor chain for repeated execution, ensuring that only subsequent advisors run during each iteration while earlier advisors execute just once.
Recursive Advisors are experimental, non-streaming, and can increase costs due to repeated LLM calls.
2. Project Setup
Before implementing recursive advisors, configure the required Spring AI dependencies using the provided Bill of Materials (BOM) to ensure version compatibility across modules. Also included is the Ollama starter for model integration.
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
The BOM ensures all Spring AI modules use compatible versions, preventing dependency conflicts. The spring-ai-starter-model-ollama starter automatically configures the integration with Ollama models, allowing applications to interact with locally running LLMs.
3. Creating a Recursive Advisor
The custom advisor below retries the LLM call until the response meets a validation condition or a maximum number of attempts is reached.
public class SupportResponseQualityAdvisor implements CallAdvisor {
private static final int MAX_RETRIES = 3;
@Override
public String getName() {
return "support-response-quality-advisor";
}
@Override
public int getOrder() {
return 100;
}
@Override
public ChatClientResponse adviseCall(ChatClientRequest originalRequest, CallAdvisorChain advisorChain) {
int retryCount = 0;
// Initial AI call
ChatClientResponse currentResponse = advisorChain.nextCall(originalRequest);
// Retry until response meets quality criteria
while (!isResponseSufficient(currentResponse) && retryCount < MAX_RETRIES) {
retryCount++;
String improvementFeedback = "The previous response was not detailed enough for a customer support scenario. "
+ "Provide a clearer, step-by-step and helpful answer.";
// Augment user message with feedback
var enhancedPrompt = originalRequest.prompt()
.augmentUserMessage(userMessage
-> userMessage.mutate()
.text(userMessage.getText() + System.lineSeparator() + improvementFeedback)
.build()
);
// Build updated request
var enhancedRequest = originalRequest.mutate()
.prompt(enhancedPrompt)
.build();
// Recursive call with updated request
currentResponse = advisorChain.copy(this).nextCall(enhancedRequest);
}
return currentResponse;
}
private boolean isResponseSufficient(ChatClientResponse response) {
String content = response.chatResponse().getResult().getOutput().getText();
// Example rule: ensure response is sufficiently detailed
return content != null && content.length() > 80;
}
}
This advisor intercepts each ChatClient request and retries the call until the response meets the defined isValid condition. chain.copy(this).nextCall(...) ensures only downstream advisors execute on each retry, preventing upstream advisors from repeating unnecessarily.
Registering the Advisor with ChatClient
To activate the recursive advisor, register it when building the ChatClient bean.
@Configuration
public class AiConfiguration {
@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultAdvisors(new SupportResponseQualityAdvisor())
.build();
}
}
Using defaultAdvisors(), we attach the custom advisor to the client. Any call made through this ChatClient automatically passes through the recursive advisor, enabling automated retries based on the defined validation logic.
Calling the Custom Advisor
Once registered, using the advisor is straightforward. All prompts sent via ChatClient automatically pass through the recursive advisor.
@Service
public class AiService {
private final ChatClient chatClient;
public AiService(ChatClient chatClient) {
this.chatClient = chatClient;
}
public String generateAnswer(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
The AiService sends user prompts through ChatClient. Because the SupportResponseQualityAdvisor is registered, any response failing the validation condition will automatically trigger a retry, returning only a validated response to the caller.
We can expose the AI service via a REST endpoint:
@RestController
public class AiController {
private final AiService aiService;
public AiController(AiService aiService) {
this.aiService = aiService;
}
@GetMapping("/support")
public String ask(@RequestParam String question) {
return aiService.generateAnswer(question);
}
}
This controller exposes a /support endpoint. When a request is made (e.g., /support?question=How+do+I+reset+my+password), it forwards the query to the service, which invokes the AI model.
Here’s a simple curl example to test the /support endpoint:
curl -X GET "http://localhost:8080/api/support?question=How+do+I+reset+my+password"
The controller receives the request, forwards it to the service, and the AI (via the recursive advisor) returns a refined, high-quality response.
Sample Response Output
To reset your password, follow these steps: 1. Go to the login page of the service or website you're using (e.g., Google, Facebook, etc.). 2. Click on "Forgot Password" or a similar option, which is usually located near the login fields. 3. You will be asked for your email address associated with the account. Enter it and click "Submit" or "Send Email." 4. Check your email inbox for a password reset email from the service provider. 5. Follow the instructions in the email to create a new password. This may involve clicking on a link, entering a verification code, or following specific directions provided by the service. 6. Enter and confirm your new password when prompted, then click "Reset Password" or similar button. 7. You should receive a confirmation that your password has been successfully reset. Now you can log in using your new password.
4. Built-in Recursive Advisors
Spring AI provides built-in recursive advisors for handling common iterative AI workflows such as tool execution and structured output validation. These advisors automatically repeat model calls when needed, reducing boilerplate and improving reliability in applications.
4.1 ToolCallAdvisor
The ToolCallAdvisor enables the model to invoke external tools and iteratively process their results until no further tool calls are required.
Define the Tool Input and Tool
public record DeliveryRequest(String trackingId) {
}
public class DeliveryTools {
public static FunctionToolCallback getDeliveryStatusTool() {
return FunctionToolCallback.builder(
"getDeliveryStatus",
(DeliveryRequest request) -> "Package " + request.trackingId() + " is currently in transit."
)
.description("Gets delivery status for a tracking ID")
.inputType(DeliveryRequest.class)
.build();
}
}
Here, we define a tool that simulates fetching delivery status using a tracking ID. The FunctionToolCallback exposes this logic to the AI model so it can call it dynamically when needed.
Configure ToolCallAdvisor and ChatClient
@Configuration
public class ToolAdvisorConfig {
@Bean
public ChatClient chatClient(ChatModel chatModel, ToolCallingManager toolCallingManager) {
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var deliveryTool = DeliveryTools.getDeliveryStatusTool();
return ChatClient.builder(chatModel)
.defaultToolCallbacks(deliveryTool)
.defaultAdvisors(toolCallAdvisor)
.build();
}
}
The ToolCallAdvisor is configured with a ToolCallingManager, which orchestrates tool execution. The tool is registered using defaultToolCallbacks(), and the advisor is added via defaultAdvisors(). This setup enables automatic tool invocation during model execution.
Using the ToolCallAdvisor
@Service
public class DeliveryService {
private final ChatClient chatClient;
public DeliveryService(ChatClient chatClient) {
this.chatClient = chatClient;
}
public String getDeliveryUpdate(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
When a user asks a question like “Where is my package with tracking ID 123?”, the model can call the getDeliveryStatus tool. The ToolCallAdvisor manages the loop of tool invocation and response refinement until a final answer is produced.
4.2 StructuredOutputValidationAdvisor
The StructuredOutputValidationAdvisor ensures that model responses conform to a defined JSON schema by validating the output and triggering retries when necessary. If validation fails, it enhances the prompt with specific error feedback and re-invokes the model until a valid structured response is produced or the retry limit is reached.
Define the Structured Output Model
public record CustomerOrderSummary(String customerName, List<String> orders) {
}
This record defines the expected structure of the AI response. The advisor will validate the model output against this schema.
Configure StructuredOutputValidationAdvisor
@Configuration
public class ValidationAdvisorConfig {
@Bean
public ChatClient chatClient(ChatModel chatModel) {
var validationAdvisor = StructuredOutputValidationAdvisor.builder()
.outputType(CustomerOrderSummary.class)
.maxRepeatAttempts(3)
.build();
return ChatClient.builder(chatModel)
.defaultAdvisors(validationAdvisor)
.build();
}
}
The advisor is configured with an output type and retry limit. If the AI response does not match the CustomerOrderSummary structure, the advisor automatically retries until it produces valid output or reaches the retry limit.
Using Structured Output Validation
@Service
public class OrderSummaryService {
private final ChatClient chatClient;
public OrderSummaryService(ChatClient chatClient) {
this.chatClient = chatClient;
}
public CustomerOrderSummary getOrderSummary(String requestPrompt) {
return chatClient.prompt()
.user(requestPrompt)
.call()
.entity(CustomerOrderSummary.class);
}
}
When a prompt like “Generate a summary of orders for Thomas” is sent, the model must return structured data matching CustomerOrderSummary. If it fails, the StructuredOutputValidationAdvisor retries automatically, ensuring consistent and reliable structured responses.
5. Conclusion
In this article, we explored Spring AI Recursive Advisors and how they enable iterative AI workflows within the advisor chain. Recursive advisors make it easier to build self-improving AI agents, tool execution loops, and structured output validation pipelines directly inside Spring applications. By combining them with Spring AI’s advisor architecture, we can create maintainable AI workflows without manually managing complex retry logic in application code.
6. Download the Source Code
This article explored how to implement and use recursive advisors within Spring AI applications.
You can download the full source code of this example here: spring ai recursive advisors




