Chapter 2- Giving Your AI Agent a Personality and Memory with Spring AI
Give Your Agent a Personality
A language model responds differently depending on the instructions it receives.
Instead of allowing the model to answer every question generically, we can define its role using a System Prompt.
A system prompt acts like a job description for the AI.
It tells the model:
- Who it is
- What its responsibilities are
- How it should respond
- What it should avoid
This allows us to create specialized AI agents without changing our application logic.
Example 1 – Enterprise Java Architect
Suppose we want our agent to behave like a Senior Java Architect.
Update the configuration.
@Configuration
public class AIConfig {
@Bean
ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("""
You are a Senior Java Architect.
Responsibilities:
- Design scalable enterprise applications.
- Recommend Spring Boot best practices.
- Follow SOLID principles.
- Generate clean production-ready Java code.
- Explain architectural decisions.
Never generate code without explaining why.
""")
.build();
}
}
Now ask:
Design a REST API for Employee Management.
Typical response:
Recommended Architecture
• Controller Layer
• Service Layer
• Repository Layer
• DTO Layer
Recommended Endpoints
POST /employees
GET /employees
GET /employees/{id}
PUT /employees/{id}
DELETE /employees/{id}
Best Practices
• Use DTOs.
• Validate requests.
• Add exception handling.
• Secure APIs with Spring Security.
Notice how the response focuses on architecture rather than simply generating code.
Example 2 – Database Performance Expert
Change the system prompt.
You are a Senior PostgreSQL Database Administrator.
Responsibilities:
- Optimize SQL queries.
- Recommend indexes.
- Explain execution plans.
- Never suggest inefficient SQL.
User:
How can I improve this query?
SELECT * FROM orders WHERE customer_id = 100;
Typical response:
Recommendations
1. Create an index on customer_id.
2. Avoid SELECT *.
3. Use EXPLAIN ANALYZE.
4. Retrieve only required columns.
The same LLM now behaves like a database expert.
Example 3 – AI Solution Architect
System Prompt
You are an AI Solution Architect.
Responsibilities:
- Design enterprise AI Agent platforms.
- Recommend Spring AI components.
- Use MCP for external tool integration.
- Explain RAG, Memory and Tool Calling.
User
How should I design an AI Agent Platform?
Response
Recommended Architecture
• Spring Boot REST APIs
• Agent Layer
• Spring AI
• MCP Client
• MCP Servers
• Vector Database
• RAG Pipeline
• Monitoring
Again, the underlying LLM has not changed—only the role it has been instructed to play.
Why System Prompts Matter
A well-designed system prompt makes your AI:
- More consistent
- Easier to control
- Domain-specific
- Production-ready
Instead of creating separate applications for every use case, you can create multiple specialized AI agents simply by changing the system prompt.
Examples include:
- HR Assistant
- Finance Assistant
- Customer Support Agent
- DevOps Engineer
- Java Architect
- AI Solution Architect
All of them can use the same underlying LLM while behaving very differently.
What We’ve Built
By the end of this chapter, we have successfully built:
- A Spring Boot application
- A Spring AI integration
- An AI-powered REST API
- A reusable
ChatClient - Our first AI agent
- A configurable System Prompt that gives the agent a specific personality
The application flow now looks like this:
Client
│
▼
AgentController
│
▼
AssistantAgent
│
▼
ChatClient
│
├── System Prompt
└── User Prompt
│
▼
Chat Model
│
▼
Response
In the next chapter, we’ll enhance our AI agent with Conversation Memory, enabling it to maintain context across multiple requests and deliver a much more natural conversational experience.
Make Your AI Agent Remember Conversations with Chat Memory
So far, we’ve built an AI agent that has a well-defined personality using a System Prompt. Every request is answered as if it were coming from a Senior Java Architect (or any other role we’ve configured).
However, there is still a major limitation.
Our agent treats every request as a completely new conversation.
Consider the following interaction.
Request 1
Generate the quarterly sales report for Q2 2026.
Response
The quarterly sales report for Q2 2026 has been generated successfully.
A few seconds later, the user sends another request.
Request 2
Export it as a PDF.
Ideally, the AI should understand that “it” refers to the quarterly sales report generated in the previous request.
Instead, without memory, the AI will most likely respond:
Could you clarify what you would like me to export as a PDF?
Why?
Because every REST request is independent. The LLM only receives the current prompt unless we explicitly provide the previous conversation.
Why Chat Memory Is Needed
Large Language Models do not automatically remember previous requests.
Every API call looks like this:
Current User Message
│
▼
Chat Model
│
▼
AI Response
The model has no knowledge of earlier interactions unless your application includes them in the request.
This means that conversation memory is the responsibility of the application—not the LLM.
How Spring AI Solves This
Spring AI provides Chat Memory, which automatically stores previous conversations and includes them in future prompts.
Instead of sending only the current message, Spring AI sends both the previous conversation and the latest request.
Current Request
│
▼
MessageChatMemoryAdvisor
│
┌───────────────┴────────────────┐
│ │
Previous Conversation Current Message
│ │
└───────────────┬────────────────┘
▼
Chat Model
│
▼
Context-Aware Response
This allows the AI to understand references such as:
- “Export it.”
- “Email the report.”
- “Summarize the previous result.”
- “Create a chart from the same data.”
Add Chat Memory Dependency
Add the following dependency.
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
For this tutorial, we’ll use an in-memory repository. In a production application, you would typically use a persistent repository backed by a relational database or another storage technology.
Configure Chat Memory
Update the AI configuration.
@Configuration
public class AIConfig {
@Bean
ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(new InMemoryChatMemoryRepository())
.maxMessages(20)
.build();
}
@Bean
ChatClient chatClient(ChatModel chatModel,
ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("""
You are a Senior Java Architect.
Responsibilities:
- Design enterprise applications.
- Recommend Spring Boot best practices.
- Generate production-ready code.
""")
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory)
.build()
)
.build();
}
}
Let’s understand what we’ve added.
The ChatMemory bean stores previous conversations.
MessageWindowChatMemory.builder()
.maxMessages(20)
This configuration keeps the most recent 20 messages in memory. Older messages are automatically discarded to prevent prompts from becoming excessively large.
Next, we register a MessageChatMemoryAdvisor.
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
The advisor has two responsibilities:
- Retrieve previous messages before sending the prompt to the LLM.
- Store the latest user message and AI response after the interaction.
This means you don’t need to manually maintain conversation history.
Update the AI Agent
Modify the service so that each request is associated with a conversation.
@Service
public class AssistantAgent {
private final ChatClient chatClient;
public AssistantAgent(ChatClient chatClient) {
this.chatClient = chatClient;
}
public String chat(String conversationId,
String message) {
return chatClient.prompt()
.user(message)
.advisors(advisor ->
advisor.param(
ChatMemory.CONVERSATION_ID,
conversationId
))
.call()
.content();
}
}
Notice that we’ve introduced a new parameter called conversationId.
This identifier allows Spring AI to retrieve the correct conversation history.
Update the Request Object
Instead of accepting only the user’s message, we’ll also include the conversation identifier.
public record ChatRequest(
String conversationId,
String message
) {
}
Update the Controller
@RestController
@RequestMapping("/agent")
public class AgentController {
private final AssistantAgent assistantAgent;
public AgentController(AssistantAgent assistantAgent) {
this.assistantAgent = assistantAgent;
}
@PostMapping
public String chat(@RequestBody ChatRequest request) {
return assistantAgent.chat(
request.conversationId(),
request.message()
);
}
}
Testing Chat Memory
First Request
POST /agent
{
"conversationId":"123e4567-e89b-12d3-a456-426614174000",
"message":"Generate the quarterly sales report for Q2 2026."
}
Response
The quarterly sales report for Q2 2026 has been generated successfully.
Second Request
POST /agent
{
"conversationId":"123e4567-e89b-12d3-a456-426614174000",
"message":"Export it as a PDF."
}
Response
The quarterly sales report has been exported as a PDF.
Because both requests share the same conversationId, Spring AI retrieves the previous conversation and understands that “it” refers to the quarterly sales report.
A Different Conversation
Now send the same request using another conversation.
{
"conversationId":"987f6543-a21b-45d7-a123-987654321000",
"message":"Export it as a PDF."
}
Response
Could you clarify what you would like me to export as a PDF?
Since this is a new conversation, there is no previous context available.
How Conversation IDs Work
A common question is:
Who generates the
conversationId?
In production systems, the backend usually creates a conversation when a user starts a new chat.
User
│
▼
POST /conversations
│
▼
Spring Boot
│
Generate UUID
│
Store Conversation
│
▼
Return conversationId
│
▼
UI stores conversationId
│
▼
POST /agent
The frontend stores the returned conversationId and includes it in every subsequent request.
This allows:
- Multiple conversations per user.
- Conversation history.
- Conversation resume.
- Proper isolation between users.
What We’ve Learned
By adding Chat Memory, our AI agent can now:
- Maintain conversation context.
- Understand follow-up requests.
- Provide more natural interactions.
- Support long-running conversations.
The architecture has evolved into the following:
Client
│
▼
AgentController
│
▼
AssistantAgent
│
▼
MessageChatMemoryAdvisor
│
▼
Chat Memory
│
Previous Messages
│
Current Message
│
▼
Chat Model
│
▼
Context-Aware Response
Our AI agent is no longer a stateless chatbot. It can now maintain context across multiple interactions, bringing it one step closer to a production-ready enterprise AI assistant.
In the next chapter, we’ll connect our AI agent to external enterprise systems using the Model Context Protocol (MCP), enabling it to retrieve live data and perform real-world actions instead of relying solely on the LLM’s built-in knowledge.