Spring AI Recipe: Composing ChatClient Behavior
In a previous recipe, ChatClientBuilderCustomizer was used to add conversational memory to a simple chat loop.
That same mechanism becomes even more useful as you begin adding tools, system prompts, and other behavior to a ChatClient.
This recipe demonstrates how to use ChatClientBuilderCustomizer to compose ChatClient behavior in a modular, maintainable way.
NOTE: This recipe has been updated since it’s original publication to reflect changes in Spring AI 2.0.0. Particularly,
ChatClientCustomerhas been changed toChatClientBuilderCustomizer.
Defining a simple tool
Suppose you want to add a tool that retrieves weather information by ZIP code:
@Service
public class WeatherTools {
@Tool(name = "get-weather-for-zipcode",
description = "Get weather for a given zipcode")
public Weather getWeatherForZipcode(
@ToolParam(description = "The zipcode to get the weather for") String zipcode) {
return new Weather(zipcode, "Raining cats and dogs", "81.5F");
}
}For completeness, here is the Weather record:
public record Weather(
String zipcode,
String conditions,
String temperature) {
}The direct approach
One way to configure this tool is directly in the ChatClient bean:
@Bean
ChatClient chatClient(
ChatClient.Builder chatClientBuilder, WeatherTools weatherTools) {
return chatClientBuilder
.defaultTools(weatherTools)
.build();
}While this works, it introduces additional parameters into the bean method and becomes harder to maintain as more features are added.
Composing behavior with ChatClientBuilderCustomizer
A cleaner approach is to use ChatClientBuilderCustomizer . For example, here are two such ChatClientBuilderCustomizer bean methods:
@Bean
ChatClientBuilderCustomizer defaultTools(WeatherTools tools) {
return builder -> builder.defaultTools(tools);
}
@Bean
ChatClientBuilderCustomizer pirateTalk() {
return builder -> builder.defaultSystem("Talk like a pirate");
}Each customizer is responsible for a single aspect of behavior:
- One adds tools
- One configures the system prompt
The base ChatClient bean can remain simple and focused.
Trying it out
If you are using the chat loop from the previous recipe, you can interact with it like this:
> What is the weather in Jal, New Mexico?
- Arrr! In Jal, New Mexico (ZIP 88252) it be rainin' cats an' dogs...The response:
- Uses the weather tool
- Applies the pirate-style system message
Why this matters
As you begin adding more capabilities to ChatClient, a single configuration method can quickly become difficult to manage.
Using ChatClientBuilderCustomizer provides:
- Separation of concerns
- Composability
- Improved readability
- Easier extension and reuse
Each behavior can be added independently without cluttering the core configuration.
Conditional configuration
Another advantage of this approach is the ability to enable or disable features via configuration. First, annotate the ChatClientBuilderCustomizer beans with @ConditionalOnProperty :
@Bean
@ConditionalOnProperty("chatclient.tools.weather.enabled")
ChatClientBuilderCustomizer defaultTools(WeatherTools tools) {
return builder -> builder.defaultTools(tools);
}
@Bean
@ConditionalOnProperty("chatclient.pirate.enabled")
ChatClientBuilderCustomizer pirateTalk() {
return builder -> builder.defaultSystem("Talk like a pirate");
}Then enable or disable them in configuration (e.g., application.properties or similar):
chatclient.tools.weather.enabled=true
chatclient.pirate.enabled=falseThis allows you to toggle capabilities using:
- application properties
- environment variables
- system properties
- configuration servers
Takeaway
ChatClientBuilderCustomizer is more than a convenience—it’s a pattern for composing ChatClient behavior in a structured and maintainable way.
As your applications evolve, this approach makes it much easier to introduce new capabilities and manage complexity — especially in more advanced, agentic scenarios. Watch for me to use ChatClientBuilderCustomizer in future recipes.
Get the code for this recipe (and all of my other Spring AI recipes) at https://github.com/habuma/spring-ai-recipes.