Enterprise Java

Spring AI Short Term Memory Sessions Example

Large Language Models are fundamentally stateless. If an application sends the question “What is my favorite programming language?” to a model, the model cannot automatically know that the same user said “My favorite programming language is Java” several requests earlier. Applications therefore need a mechanism for preserving conversational context. Spring AI has traditionally provided the ChatMemory abstraction for this purpose. The newer Spring AI Session project introduces a more structured approach based on sessions, immutable conversation events, turn-aware compaction, and the SessionMemoryAdvisor. This article explains how short-term memory sessions work in Spring AI, what a memory session represents, why context compaction is necessary, the available compaction strategies, and how to build a Spring Boot application that creates and manages conversations using SessionMemoryAdvisor.

1. Overview

When developers hear the word memory in an AI application, it is useful to distinguish between short-term and long-term memory. Short-term memory maintains context within the current conversation or task, allowing the AI assistant to recall recently shared information such as a user’s preferred framework, while long-term memory preserves information across different conversations or sessions, enabling the assistant to remember persistent user preferences or important details even after several days or weeks. Spring AI Session focuses primarily on the first category: maintaining a structured history for an active conversational session. Instead of treating conversation memory simply as a list of messages, Spring AI Session represents the conversation as a series of events associated with a session. The central components are:

  • Session — identifies a conversational session.
  • SessionEvent — represents an event belonging to the session.
  • SessionService — provides operations over sessions and their event history.
  • SessionRepository — persists session information.
  • SessionMemoryAdvisor — connects session memory to Spring AI’s ChatClient.
  • CompactionTrigger — determines when memory should be compacted.
  • CompactionStrategy — determines how conversation context should be reduced.

1.1 What are Memory Sessions?

A memory session represents a bounded conversational context between an application and a user or agent. For example, if a user initially says, “My name is Rahul” and later mentions, “I primarily work with Java and Spring Boot,” the session stores this conversational context so that when the user subsequently asks, “Which framework did I say I use?”, the assistant can correctly respond that the user primarily uses Spring Boot. Without memory, the model would receive only the latest question and would have no reliable way to determine which framework had been mentioned earlier. With session memory, the earlier interactions are loaded before the current prompt is sent to the model. Conceptually the model receives something closer to:

User: My name is Rahul.
Assistant: Nice to meet you, Rahul.

User: I primarily work with Java and Spring Boot.
Assistant: Great. I will keep that context in mind.

User: Which framework did I say I use?

The SessionMemoryAdvisor handles this process automatically.

1.1.1 Session Events

Spring AI Session follows an event-sourced approach in which conversational interactions are represented as SessionEvent objects rather than being stored as a simple list of text messages. Each session event can include details such as a unique event identifier, session identifier, timestamp, the underlying Spring AI message, optional branch information for multi-agent conversations, and framework-related metadata. This structured approach is particularly useful for AI agent applications because a conversation can include not only user and assistant messages but also tool calls, tool results, and other events required to preserve the complete conversational context.

1.1.2 Why Use a Session ID?

The session ID separates one conversation from another. For example:

session-1001
    User: My preferred language is Java.
    Assistant: Understood.

session-2002
    User: My preferred language is Python.
    Assistant: Understood.

A request using session-1001 should receive the Java conversation history, while a request using session-2002 should receive the Python conversation history. When calling the ChatClient, the session ID can be supplied to SessionMemoryAdvisor through the advisor context:

chatClient.prompt()
    .user("What language did I say I prefer?")
    .advisors(a -> a.param(
        SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY,
        sessionId))
    .call()
    .content();

1.2 Why Is Memory Compaction Necessary?

The simplest memory approach may seem to be sending the complete conversation history to the model with every request. While this can work well for short conversations, it becomes inefficient as a session grows from a few turns to hundreds of interactions. Continuously including the entire history increases token consumption, raises model usage costs, can increase response latency, and may eventually exceed the model’s context-window limit. In addition, excessive older information can make it harder for the model to focus on the most relevant parts of the current task. This is where compaction becomes useful. Compaction reduces the amount of conversation history included in the model’s active context while preserving the conversation structure required to continue the session.

1.3 Why Naive Truncation Can Be Dangerous?

A basic memory-truncation approach might remove the oldest individual messages whenever the conversation history becomes too large, but this can create inconsistencies in agent-based workflows that involve tool calls. For example, an assistant may decide to invoke a tool such as getWeather(), receive a tool result, and then use that result to generate the final response. If truncation removes the assistant’s original tool-call message while leaving the corresponding tool result in memory, the remaining context becomes logically incomplete. To avoid this problem, Spring AI Session uses turn-aware compaction, ensuring that the retained context respects complete conversation boundaries instead of arbitrarily removing individual messages from the middle of an interaction.

1.4 Compaction Triggers and Strategies

Spring AI Session separates memory compaction into two key decisions: when compaction should occur and how the conversation history should be compacted. The timing of compaction is determined by a CompactionTrigger, which monitors conditions such as the number of conversation turns or token usage, while a CompactionStrategy defines how older conversation data is reduced, retained, or summarized once the trigger condition is met.

  • TurnCountTrigger: A TurnCountTrigger initiates compaction after the conversation reaches a configured number of turns. For example, new TurnCountTrigger(20) sets the threshold to 20 turns. The system monitors conversation growth and invokes the configured compaction strategy when the turn-count threshold is reached.
  • TokenCountTrigger: A TokenCountTrigger initiates compaction based on the estimated number of tokens in the conversation. This approach is useful because an LLM’s practical limitation is typically its context-window token budget rather than simply the number of messages or conversation turns.

1.5 Available Compaction Strategies

StrategyPurposeTypical Use Case
SlidingWindowCompactionStrategyKeeps a recent window of conversation events.General chatbots and simple conversational applications.
TurnWindowCompactionStrategyKeeps a configured number of complete conversational turns.Agent workflows where preserving complete interactions is important.
TokenCountCompactionStrategyKeeps the conversation inside a token-oriented budget.Applications that need tighter control over model context size.
RecursiveSummarizationCompactionStrategyUses an LLM-generated rolling summary to preserve information from older conversation history.Long-running agents or conversations where older context remains important.

1.6 Short-Term Memory vs Traditional ChatMemory

Spring AI’s traditional ChatMemory API is still important, but the Session API introduces a richer conversation model.

FeatureTraditional ChatMemorySpring AI Session
Basic conversation contextYesYes
Primary stored unitMessagesStructured session events
Session metadataLimited conversation-ID modelExplicit Session abstraction
CompactionTypically memory/window policiesDedicated triggers and strategies
Turn-aware compactionNot the central abstractionYes
Recursive summarizationNot the standard message-window behaviorBuilt-in strategy
Multi-agent branch isolationNot a core featureSupported
Event identity and timestampsMessage-centricEvent-sourced model

2. Complete Spring Boot Example

2.1 Maven Dependencies

To build the Spring AI Session application, add the required Spring Boot, Spring AI, session persistence, and database dependencies to the project’s pom.xml file. These dependencies provide the components needed to expose REST APIs, communicate with an OpenAI model, and persist conversational session data.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springaicommunity</groupId>
        <artifactId>spring-ai-starter-session-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

In this configuration, spring-boot-starter-web provides the web and REST API infrastructure, while spring-ai-starter-model-openai integrates the application with OpenAI models through Spring AI. The spring-ai-starter-session-jdbc dependency provides JDBC-backed session storage for maintaining conversational memory, and the h2 dependency supplies a lightweight in-memory database that can be used to store session information during development and testing.

2.2 Application Configuration

Next, configure the Spring Boot application by creating the src/main/resources/application.yml file. This configuration defines the application name, OpenAI model settings, H2 datasource connection, and the HTTP port on which the application will run.

spring:
  application:
    name: spring-ai-session-demo
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4.1-mini
          temperature: 0.2
  datasource:
    url: jdbc:h2:mem:sessiondb
    driver-class-name: org.h2.Driver
    username: sa
    password:
server:
  port: 8080

In this configuration, spring.application.name defines the application name, while the spring.ai.openai properties configure the OpenAI API key and chat model settings. The application uses gpt-4.1-mini with a temperature of 0.2 to produce relatively consistent responses. The spring.datasource properties configure an in-memory H2 database named sessiondb for storing session-related data, and server.port runs the application on port 8080. For security, the OpenAI API key should not be hard-coded in the configuration file; instead, supply it through the OPENAI_API_KEY environment variable, for example: export OPENAI_API_KEY=your-api-key.

2.3 Main Spring Boot Application

Create the main Spring Boot application class to serve as the entry point for the Spring AI Session application. The class initializes the Spring application context, enables auto-configuration, and starts the embedded web server.

// SessionMemoryApplication.java
package com.example.memory;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SessionMemoryApplication {

    public static void main(String[] args) {
        SpringApplication.run(SessionMemoryApplication.class, args);
    }
}

The SessionMemoryApplication class is annotated with @SpringBootApplication, which combines Spring Boot’s configuration, auto-configuration, and component-scanning capabilities. The main() method calls SpringApplication.run() to bootstrap the application, create the Spring application context, discover configured components and beans, and start the embedded web server so that the REST endpoints can accept incoming requests.

2.4 Configuring SessionMemoryAdvisor

Next, configure the SessionMemoryAdvisor and ChatClient as Spring beans. The advisor connects session-based conversational memory with the Spring AI ChatClient, while the compaction configuration controls how the active conversation context is reduced as the session grows.

// AiConfiguration.java
package com.example.memory;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springaicommunity.session.SessionService;
import org.springaicommunity.session.advisor.SessionMemoryAdvisor;
import org.springaicommunity.session.compaction.SlidingWindowCompactionStrategy;
import org.springaicommunity.session.compaction.TurnCountTrigger;

@Configuration
public class AiConfiguration {

    @Bean
    SessionMemoryAdvisor sessionMemoryAdvisor(SessionService sessionService) {
        return SessionMemoryAdvisor.builder(sessionService)
                // Compact the active conversation after it grows.
                .compactionTrigger(new TurnCountTrigger(6))
                // Keep a smaller recent event window.
                .compactionStrategy(
                        SlidingWindowCompactionStrategy.builder()
                                .maxEvents(6)
                                .build())
                .build();
    }

    @Bean
    ChatClient chatClient(ChatModel chatModel, SessionMemoryAdvisor sessionMemoryAdvisor) {
        return ChatClient.builder(chatModel)
                .defaultAdvisors(sessionMemoryAdvisor)
                .build();
    }
}

The AiConfiguration class defines the components required to integrate session memory with the AI conversation flow. The sessionMemoryAdvisor() bean receives the auto-configured SessionService, which manages session data and conversation events, and uses it to build a SessionMemoryAdvisor. The TurnCountTrigger(10) determines when compaction should be considered based on conversation growth, while SlidingWindowCompactionStrategy with maxEvents(6) limits the active context to a smaller window of recent events while respecting valid conversation boundaries. The chatClient() bean then registers the SessionMemoryAdvisor as a default advisor, allowing it to participate automatically in ChatClient requests by retrieving relevant session history, adding it to the model context, recording new conversation events, and applying the configured compaction policy as the session grows.

The values used in this example are intentionally small so that the effects of compaction can be observed easily. In a production application, the thresholds should be selected based on the model’s context window, expected conversation length, token usage, and application requirements.

2.5 Creating a Session

Before implementing the REST controller, define a few lightweight Data Transfer Objects (DTOs) to exchange session and chat information between the client and the application. Java record types are used here because they provide a concise way to represent immutable request and response data.

2.5.1 Response DTO

The SessionResponse record represents the response returned after a new conversation session is created. It contains the generated session identifier along with the user identifier associated with the session.

// SessionResponse.java
package com.example.memory;

public record SessionResponse(String sessionId, String userId) { }

The sessionId uniquely identifies the conversational session and should be supplied with subsequent chat requests so that Spring AI can retrieve the correct conversation history. The userId identifies the user associated with the session, allowing the application to distinguish user identity from individual conversations.

2.5.2 Chat Request

The ChatRequest record represents an incoming request to the chat REST endpoint. It carries both the session identifier and the new message that should be processed by the AI model.

// ChatRequest.java
package com.example.memory;

public record ChatRequest(String sessionId, String message) { }

The sessionId associates the incoming message with an existing conversation, enabling the SessionMemoryAdvisor to retrieve the relevant short-term memory, while the message field contains the user’s current prompt. Reusing the same session ID across requests allows the assistant to maintain conversational continuity.

2.5.3 Chat Response

The ChatResponse record defines the response returned to the client after the AI model processes a message. It includes the session identifier and the generated assistant response.

// ChatResponse.java
package com.example.memory;

public record ChatResponse(String sessionId, String answer) { }

The sessionId allows the client to associate the response with the correct conversation, while the answer field contains the text generated by the AI model. Returning the session ID with each response also makes it easier for client applications to maintain and continue multiple independent conversations.

2.6 Creating and Using Sessions in the Controller

The REST controller brings the session-management and conversational-memory components together. It provides one endpoint for creating a new session and another for sending messages through the ChatClient while associating each request with the appropriate session ID.

// ChatController.java
package com.example.memory;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.*;

import org.springaicommunity.session.CreateSessionRequest;
import org.springaicommunity.session.Session;
import org.springaicommunity.session.SessionService;
import org.springaicommunity.session.advisor.SessionMemoryAdvisor;

@RestController
@RequestMapping("/api")
public class ChatController {
  private final ChatClient chatClient;
  private final SessionService sessionService;

  public ChatController(ChatClient chatClient, SessionService sessionService) {
    this.chatClient = chatClient;
    this.sessionService = sessionService;
  }

  @PostMapping("/sessions")
  public SessionResponse createSession(@RequestParam String userId) {
    Session session = sessionService.create(CreateSessionRequest.builder().userId(userId).build());
    return new SessionResponse(session.id(), userId);
  }

  @PostMapping("/chat")
  public ChatResponse chat(@RequestBody ChatRequest request) {
    String answer = chatClient
                .prompt()
                .system("""
                        You are a helpful technical assistant.
                        Remember information supplied earlier in the
                        current conversation and use it when relevant.
                        """)
                .user(request.message())
                .advisors(advisorSpec ->
                        advisorSpec.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, request.sessionId()))
                .call()
                .content();
        return new ChatResponse(request.sessionId(), answer);
  }
}

The ChatController exposes its endpoints under /api and receives both the ChatClient and SessionService through constructor injection. The /sessions endpoint creates a new conversational session for the supplied userId by calling sessionService.create() and returns the generated session ID to the client. The /chat endpoint accepts a ChatRequest, creates a prompt containing the system instructions and the user’s current message, and passes the session ID to SessionMemoryAdvisor through SESSION_ID_CONTEXT_KEY. This allows the advisor to associate the request with the correct conversation, retrieve relevant previous session events, include that history in the model context, and record the new interaction after the model responds. Finally, .call().content() executes the AI request and extracts the generated response, which is returned to the client inside a ChatResponse together with the same session ID.

2.7 Running the Application

After completing the application configuration and Java classes, start the Spring Boot application from the project directory using Maven. Ensure that the OPENAI_API_KEY environment variable has already been configured so that Spring AI can communicate with the OpenAI model.

mvn spring-boot:run

Once the application starts successfully, the embedded web server runs on port 8080, making the REST endpoints available through localhost:8080. The following requests demonstrate how to create a session, maintain conversational memory across multiple requests, and isolate the memory of different sessions.

2.7.1 Creating a New Session

Before starting a conversation, create a new session for the user by calling the /api/sessions endpoint and supplying a userId. The application creates a unique session that can be used for subsequent chat requests.

POST /api/sessions?userId=rahul

An example response is shown below. The generated sessionId uniquely identifies this conversation and should be included in subsequent requests that belong to the same conversational context.

{
  "sessionId": "5d787756-46f0-4cf8-86ea-bfa50fcb68f7",
  "userId": "rahul"
}

The actual session identifier will vary each time a new session is created. In this example, the generated session ID is associated with Rahul’s conversation and will be reused in the following requests.

2.7.2 Sending the First Conversation Request

After creating the session, send a message to the /api/chat endpoint using the generated session ID. In this first interaction, the user provides information that should become part of the session’s short-term conversational memory.

POST /api/chat
Content-Type: application/json

{
  "sessionId": "5d787756-46f0-4cf8-86ea-bfa50fcb68f7",
  "message": "My name is Rahul and my favorite programming language is Java."
}

A response from the model is:

{
  "sessionId": "5d787756-46f0-4cf8-86ea-bfa50fcb68f7",
  "answer": "Nice to meet you, Rahul. I'll keep in mind that your favorite programming language is Java."
}

The interaction is associated with the supplied session ID, allowing SessionMemoryAdvisor to preserve the relevant conversational events. This means that information such as the user’s name and preferred programming language can be made available to later requests within the same session.

2.7.3 Sending a Second Request Using the Same Session

To verify that short-term memory is working, send another request using exactly the same sessionId. This time, ask the model about information that was supplied only in the previous interaction.

POST /api/chat
Content-Type: application/json

{
  "sessionId": "5d787756-46f0-4cf8-86ea-bfa50fcb68f7",
  "message": "What is my favorite programming language?"
}

A response is:

{
  "sessionId": "5d787756-46f0-4cf8-86ea-bfa50fcb68f7",
  "answer": "Your favorite programming language is Java."
}

Notice that the second request itself does not contain the word Java. The model can still answer correctly because the request uses the same session ID, enabling SessionMemoryAdvisor to retrieve the relevant history associated with that session and include the necessary conversational context when communicating with the model.

2.7.4 Demonstrating Session Isolation

Session IDs also isolate independent conversations from one another. To demonstrate this behavior, create another session for a different user, such as Anita, by calling the session endpoint again.

POST /api/sessions?userId=anita

Suppose the newly created session returns a different identifier, represented here as session-B. Anita can then start an independent conversation using this new session ID.

{
  "sessionId": "session-B",
  "message": "My favorite programming language is Python."
}

At this point, the application logically maintains two separate conversational contexts:

Session A
------------------------
User: Rahul
Favorite language: Java

Session B
------------------------
User: Anita
Favorite language: Python

When Rahul sends another request using Session A, SessionMemoryAdvisor retrieves the conversation history associated with Rahul’s session, allowing the model to remember Java as his preferred programming language. When Anita sends a request using Session B, the advisor instead retrieves Anita’s independent conversation context, allowing the model to remember Python. This separation demonstrates why the sessionId is an essential part of the application’s conversational state: it enables Spring AI to maintain contextual continuity within a conversation while preventing the memory of one session from being mixed with another.

3. Conclusion

Short-term memory is an important capability for conversational and agentic AI applications because Large Language Models do not inherently remember information from previous API requests. Spring AI Session addresses this limitation by providing structured components such as Session, SessionEvent, SessionService, SessionRepository, and SessionMemoryAdvisor for maintaining conversational context across multiple interactions. The SessionMemoryAdvisor integrates session memory directly into the Spring AI ChatClient pipeline, allowing applications to associate requests with a session ID while the advisor manages the relevant conversation history. As conversations grow, compaction helps keep the active model context manageable. Strategies such as SlidingWindowCompactionStrategy, TurnWindowCompactionStrategy, TokenCountCompactionStrategy, and RecursiveSummarizationCompactionStrategy provide different ways to control how conversational history is retained or reduced. Overall, Spring AI Session provides a flexible foundation for building context-aware AI applications by isolating individual conversations, preserving relevant short-term information, and controlling the amount of history supplied to the model. Choosing an appropriate session-management and compaction strategy enables developers to balance conversational continuity, token usage, model context limits, and application performance as AI interactions become longer and more complex.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button