eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – All Access – NPI EA (cat= Spring)
announcement - icon

All Access is finally out, with all of my Spring courses. Learn JUnit is out as well, and Learn Maven is coming fast. And, of course, quite a bit more affordable. Finally.

>> GET THE COURSE
Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

Large language models are stateless, so every request we send is independent unless we replay the earlier conversation ourselves. Spring AI has traditionally solved this with ChatMemory, but as conversations grow, naively replaying every message quickly overflows the model’s context window.

In this tutorial, we’ll explore Spring AI Session, an event-sourced short-term memory layer that stores conversation history and shrinks it intelligently when it gets too large. We’ll create and inspect sessions, plug memory into a ChatClient through an advisor, and review the available compaction strategies.

2. What Are Memory Sessions?

A session is a container for a single conversation, identified by an ID and optionally tied to a user. Instead of storing a flat list of messages, it records an ordered stream of SessionEvent objects, each wrapping a Message with a timestamp, a unique ID, and optional branch information.

The library groups these events into turns. A turn is one user message plus every assistant reply, tool call, and tool result that follows it, until the next user message. Turns are the atomic unit the library never breaks apart.

That last point is the key improvement over the older ChatMemory API. When history grows too large, Spring AI Session compacts it along turn boundaries rather than evicting the oldest individual messages. This way, we never end up with a dangling tool call whose result was dropped. For reference, a MessageWindowChatMemory capped at 20 messages becomes a TurnCountTrigger(20) paired with a sliding-window strategy.

Every event is immutable and timestamped. This lets the same session support more advanced scenarios, such as isolating the histories of cooperating agents through branch labels.

Sessions are currently incubating in the spring-ai-community project and are slated to replace ChatMemory in a future Spring AI release.

3. Setting Up the Project

The Session API requires Spring AI 2.x and Spring Boot 4.x. We add the core module, which ships the in-memory repository and all the compaction building blocks:

<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>spring-ai-session-management</artifactId>
    <version>0.5.0</version>
</dependency>

The latest version of spring-ai-session-management is available in the Maven Repository.

To talk to a model, we add a Spring AI chat starter. The session layer is provider-agnostic, so OpenAI, Anthropic, or a local model works just as well. Here, we use spring-ai-starter-model-google-genai with our Gemini API key:

spring.ai.google.genai.api-key=${GEMINI_API_KEY}
spring.ai.google.genai.chat.options.model=gemini-3.5-flash

For production, we’d typically swap the in-memory store for a relational one. Adding the spring-ai-starter-session-jdbc starter auto-configures a JDBC repository for PostgreSQL, MySQL, MariaDB, or H2. In this tutorial, we’ll stick with the in-memory repository to keep the examples self-contained.

4. Creating and Managing Sessions

A SessionService is the entry point for the whole API. We start by exposing it as a bean backed by an InMemorySessionRepository:

@Bean
public SessionService sessionService() {
    return DefaultSessionService.builder()
      .sessionRepository(InMemorySessionRepository.builder().build())
      .build();
}

Let’s create a session, append a couple of messages, and read them back as plain Spring AI messages:

@Test
void givenSession_whenAppendingMessages_thenStoredInOrder() {
    Session session = sessionService.create(CreateSessionRequest.builder()
      .userId("alice")
      .build());

    sessionService.appendMessage(session.id(), new UserMessage("What is Spring AI?"));
    sessionService.appendMessage(session.id(),
      new AssistantMessage("It's an application framework for AI engineering."));

    List<Message> messages = sessionService.getMessages(session.id());
    assertThat(messages).hasSize(2);
    assertThat(messages.get(0).getMessageType()).isEqualTo(MessageType.USER);
    assertThat(messages.get(1).getMessageType()).isEqualTo(MessageType.ASSISTANT);
}

For lower-level access, getEvents() returns the richer SessionEvent stream, including timestamps and metadata. The service also lets us look up a conversation later with findById() or list everything for a user through findByUserId(). Finally, we can set a time-to-live on CreateSessionRequest so stale sessions expire automatically.

5. Using the SessionMemoryAdvisor

Managing the service by hand is useful, but most applications want memory to work transparently, much like the advisors we lean on when building an AI assistant. The SessionMemoryAdvisor hooks the session into the ChatClient pipeline, loading history before each call and appending the new exchange afterward. It’s a standard Spring AI advisor.

5.1. Configuring the Advisor Bean

Now, let’s expose the advisor as a bean, attaching a compaction trigger and strategy:

@Bean
public SessionMemoryAdvisor sessionMemoryAdvisor(SessionService sessionService) {
    return SessionMemoryAdvisor.builder(sessionService)
      .defaultUserId("alice")
      .compactionTrigger(new TurnCountTrigger(20))
      .compactionStrategy(SlidingWindowCompactionStrategy.builder()
          .maxEvents(10)
          .build())
      .build();
}

This configuration keeps the ten most recent events and compacts once a session passes twenty turns.

5.2. Wiring It Into ChatClient

Next, we register the advisor as a default on a ChatClient, then identify the conversation with the session-ID parameter on each call:

@Component
public class ChatService {

    private final ChatClient chatClient;

    public ChatService(ChatModel chatModel, SessionMemoryAdvisor sessionMemoryAdvisor) {
        this.chatClient = ChatClient.builder(chatModel)
          .defaultAdvisors(sessionMemoryAdvisor)
          .build();
    }

    public String chat(String sessionId, String prompt) {
        return chatClient.prompt()
          .user(prompt)
          .advisors(a -> a.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, sessionId))
          .call()
          .content();
    }
}

The advisor stores every exchange under that session ID, so a follow-up question resolves against the earlier context. In the test below, we share a name in the first message, then ask for it back in a second one. The assertion confirms the reply still recalls the name, proving the session preserved context across the two turns:

@Test
void givenSessionId_whenChattingAcrossTurns_thenContextIsRemembered() {
    chatService.chat("session-abc", "My name is Yadier, remember it.");

    String response = chatService.chat("session-abc", "What is my name?");

    assertThat(response).containsIgnoringCase("Yadier");
    assertThat(sessionService.getMessages("session-abc")).hasSize(4);
}

The second call answers with the remembered name, which confirms the advisor replayed the first turn from the session before calling the model.

6. Compaction Strategies

A compaction trigger decides when to shrink history, while a strategy decides how. Besides TurnCountTrigger, we can trigger on estimated tokens with TokenCountTrigger, or combine conditions with CompositeCompactionTrigger.

6.1. Comparing the Strategies

The library ships four strategies, all of which respect turn boundaries:

Strategy LLM required Best for
SlidingWindowCompactionStrategy No Cost-sensitive, recent-context chats
TurnWindowCompactionStrategy No Keeping the last N complete turns
TokenCountCompactionStrategy No Hard context-window limits
RecursiveSummarizationCompactionStrategy Yes Long-running sessions needing recall

The first three strategies drop older events outright, so they’re fast and free — a good default when we just need to cap history cheaply. Recursive summarization instead preserves the gist of what it removes, at the cost of an extra model call. In short, we reach for recursive summarization only when we need to recall older context, otherwise, the cheaper windowing strategies are enough.

6.2. Running Compaction Manually

The SessionMemoryAdvisor runs compaction automatically as a session grows, but we can also trigger it ourselves by calling compact() directly. That’s handy in tests or batch jobs where we decide exactly when history shrinks. Let’s use the SlidingWindowCompactionStrategy to keep only the most recent events, passing it and a trigger to compact(), which returns a CompactionResult describing what it archived:

@Test
void givenMultiTurnConversation_whenCompacting_thenOlderEventsAreArchived() {
    Session session = sessionService.create(CreateSessionRequest.builder()
      .userId("alice")
      .build());
    for (int turn = 1; turn <= 4; turn++) {
        sessionService.appendMessage(session.id(), new UserMessage("Question " + turn));
        sessionService.appendMessage(session.id(), new AssistantMessage("Answer " + turn));
    }

    CompactionResult result = sessionService.compact(session.id(),
      new TurnCountTrigger(2),
      SlidingWindowCompactionStrategy.builder()
        .maxEvents(4)
        .build());

    assertThat(result.eventsRemoved()).isPositive();
    assertThat(sessionService.getEvents(session.id())).hasSameSizeAs(result.compactedEvents());
}

First we build up four turns, then we compact the session. Finally, we verify that some events were removed and the stored history now matches the compacted result. The assertions confirm that older events were archived and that the session now holds only the compacted set.

6.3. Summarizing With an LLM

Recursive summarization is the one strategy that needs a ChatClient. The other three simply drop or window events, but this one asks a model to write a summary of the events it removes, then replaces them with a single synthetic summary event. That extra model call is the price for keeping older context available in condensed form:

@Test
void givenLongConversation_whenSummarizing_thenOlderEventsAreReplacedBySummary() {
    Session session = sessionService.create(CreateSessionRequest.builder()
      .userId("alice")
      .build());
    for (int turn = 1; turn <= 4; turn++) {
        sessionService.appendMessage(session.id(), new UserMessage("Question " + turn));
        sessionService.appendMessage(session.id(), new AssistantMessage("Answer " + turn));
    }

    ChatClient chatClient = ChatClient.builder(chatModel).build();
    CompactionResult result = sessionService.compact(session.id(),
      new TurnCountTrigger(2),
      RecursiveSummarizationCompactionStrategy.builder(chatClient)
        .maxEventsToKeep(4)
        .build());

    SessionEvent summary = result.compactedEvents().stream()
      .filter(SessionEvent::isSynthetic)
      .findFirst()
      .orElseThrow();

    assertThat(result.eventsRemoved()).isPositive();
    assertThat(summary.getMessage().getText()).isNotBlank();
}

The synthetic event in the compacted set is the model-generated summary, so the session keeps the gist of the dropped turns in condensed form.

7. Conclusion

In this article, we explored Spring AI’s short-term memory sessions. We saw how a SessionService stores conversations as turn-aware events. The SessionMemoryAdvisor makes that memory transparent to the ChatClient. Finally, pluggable triggers and strategies keep history within the model’s context window.

As the API graduates from incubation, it’s positioned to become the default replacement for ChatMemory, so investing in it now sets us up for the long term.

As always, the full source code is available over on GitHub.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
guest
0 Comments
Oldest
Newest
Inline Feedbacks
View all comments