Custom CallAdvisor & StreamAdvisor in Spring AI
As AI capabilities continue to integrate deeper into enterprise applications, observability and control over AI interactions become essential. Spring AI provides built-in extension points to hook into AI model calls, allowing developers to inspect, modify, and monitor requests and responses. Let us delve into understanding the Spring AI CallAdvisor and StreamAdvisor example to see how these powerful extension points enable custom logic around AI model interactions.
1. CallAdvisor and StreamAdvisor API
Spring AI introduces two powerful extension points to intercept and modify AI behavior in your applications.
CallAdvisor is used for synchronous, non-streaming interactions with the AI model. It enables custom hooks before and after a model call, such as modifying the prompt, injecting metadata, or auditing input/output. StreamAdvisor, on the other hand, is tailored for streaming responses, allowing chunk-by-chunk processing as the model generates content. This is particularly useful when working with token-based streaming models or implementing real-time interfaces such as chat or speech generation.
Both advisors support use cases like:
- Pre-processing prompts and post-processing AI responses
- Attaching logging or distributed tracing for observability (e.g., via Micrometer or OpenTelemetry)
- Injecting runtime context, such as user roles or feature flags
- Validating input against schema or business rules before model invocation
- Auditing requests and responses for compliance or debugging
Advisors make Spring AI highly extensible, enabling teams to build responsible and observable AI integrations.
2. Advisor Lifecycle & Composition
Spring AI executes advisors in a deterministic lifecycle that supports layered behaviors. Multiple advisors can be registered via configuration or Java-based bean definitions. At runtime, advisors are invoked in a structured order: pre-call hooks run in the order of declaration, and post-call hooks execute in reverse—ensuring proper cleanup and context teardown.
This approach makes it easy to compose functionality:
- For example, a
LoggingAdvisorcan log inputs and outputs - A
TracingAdvisorcan propagate trace context across services - A
SecurityAdvisorcan enforce access control or scrub sensitive content
You can mix and match advisors declaratively or programmatically. Spring AI even supports conditional registration of advisors using Spring profiles or custom logic, allowing different behaviors in dev, staging, or prod environments.
By enabling advisor chaining, Spring AI empowers teams to encapsulate cross-cutting concerns in reusable components—mirroring the best practices of traditional Spring AOP (Aspect-Oriented Programming).
3. Creating a Custom CallAdvisor and StreamAdvisor
Let’s create a CallAdvisor that logs input and output of the model call, and a StreamAdvisor that logs streaming content as it’s received.
3.1 Project Setup (pom.xml)
To begin using Spring AI with OpenAI, you need to set up your Maven project with the required starter dependency. This starter includes built-in support for OpenAI’s API, configuration properties, and integration with Spring Boot’s lifecycle. Add the following dependency to your pom.xml:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>latest__jar__version</version>
</dependency>
You can find the latest version on the Maven Central Repository. This dependency pulls in everything needed to work with OpenAI’s REST API, including support for chat completion, embeddings, and streaming responses. Also, make sure your project is using Spring Boot 3.2 or higher for full compatibility with the Spring AI module.
3.2 CustomCallAdvisor.java
To add custom behavior before and after calling the AI model, we implement a simple AiClientCallAdvisor named CustomCallAdvisor. This class is annotated with @Component so that Spring can auto-detect and register it. In the before() method, we log the prompt that will be sent to the model, allowing inspection or auditing of inputs. In the after() method, we log the AI response, which can be useful for debugging, tracing, or compliance. This advisor is invoked automatically for each call made by Spring AI clients like ChatClient, enabling cross-cutting concerns to be handled cleanly.
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.client.advisor.AiClientCallAdvisor;
import org.springframework.ai.client.advisor.AiClientCallContext;
import org.springframework.stereotype.Component;
@Component
public class CustomCallAdvisor implements AiClientCallAdvisor {
@Override
public void before(AiClientCallContext context) {
System.out.println("Before Call:");
System.out.println("Prompt: " + context.getPrompt());
}
@Override
public void after(AiClientCallContext context) {
System.out.println("After Call:");
System.out.println("Response: " + context.getResponse());
}
}
3.3 CustomStreamAdvisor.java
To handle streaming responses from the AI model, we create a CustomStreamAdvisor by implementing the AiClientStreamAdvisor interface. This advisor enables developers to inspect and react to the prompt before the stream starts and to observe each chunk of data as it’s received from the model. The before() method logs the prompt for visibility, while the after() method wraps the response stream using Project Reactor’s Flux API, allowing inspection or modification of each ChatResponseDelta emitted by the model. By using doOnNext(), we log each piece of content in real time—useful for building reactive UIs, streaming dashboards, or debugging output as it flows.
import org.springframework.ai.chat.stream.ChatStream;
import org.springframework.ai.chat.stream.ChatResponseDelta;
import org.springframework.ai.client.advisor.AiClientStreamAdvisor;
import org.springframework.ai.client.advisor.AiClientStreamContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
@Component
public class CustomStreamAdvisor implements AiClientStreamAdvisor {
@Override
public void before(AiClientStreamContext context) {
System.out.println("Stream - Before Call:");
System.out.println("Prompt: " + context.getPrompt());
}
@Override
public Flux after(AiClientStreamContext context, Flux stream) {
return stream.doOnNext(delta -> {
System.out.println("Streaming chunk: " + delta.getContent());
});
}
}
3.4 Controller to Trigger AI Call
This REST controller provides a simple endpoint to trigger an AI chat completion using Spring AI’s ChatClient. When a GET request is made to /ask with a question parameter, it wraps the input into a prompt and sends it to the OpenAI model. The ChatClient internally triggers any registered advisors, such as CustomCallAdvisor or CustomStreamAdvisor, before and after the model call. The response content is extracted from the result and returned as plain text to the client.
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AiController {
private final ChatClient chatClient;
public AiController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ask")
public String ask(@RequestParam String question) {
Prompt prompt = new Prompt("Q: " + question + "\nA:");
return chatClient.call(prompt).getResult().getOutput().getContent();
}
}
3.5 application.yml
To connect with the OpenAI service, you must provide your API key in the Spring Boot configuration file. Spring AI automatically reads this property when initializing the ChatClient. Ensure this key is kept secure and never hardcoded in production environments. You can also load it via environment variables or secret management tools.
spring:
ai:
openai:
api-key: YOUR_API_KEY
3.6 Code Run and Output
Once the application is running, you can test the full flow by accessing the AI controller endpoint in a browser or using tools like curl or Postman. When the following URL is hit, the request is processed by ChatClient, and both CustomCallAdvisor and CustomStreamAdvisor are triggered automatically. You will see logging in the console showing the prompt sent, the model’s full response, and each streamed chunk as it’s received, demonstrating the advisor lifecycle in action.
http://localhost:8080/ask?question=What is Spring AI?
Output in console:
Before Call: Prompt: Q: What is Spring AI? A: After Call: Response: Spring AI is a framework to integrate AI models into Spring apps. Streaming chunk: Spring Streaming chunk: AI Streaming chunk: is Streaming chunk: a Streaming chunk: powerful ...
4. Conclusion
In this article, we learned what CallAdvisor and StreamAdvisor are in Spring AI, how the advisor lifecycle works, how to build custom advisors to log and trace AI calls, and how to plug these into a real Spring Boot application. These extension points give developers full control over AI communication, enabling use cases like observability, validation, and response enrichment. You can further extend this foundation to implement metrics collection, audit trails, security filters, or even intelligent fallback strategies when working with multiple AI providers.




