diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/pom.xml
new file mode 100644
index 00000000000..5905237372b
--- /dev/null
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/pom.xml
@@ -0,0 +1,110 @@
+
+
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai-parent
+ 1.1.0-SNAPSHOT
+ ../../../../../../pom.xml
+
+
+ spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch
+ Spring AI Auto Configuration - Chat Memory Repository - Elasticsearch
+ Spring AI Auto Configuration for Elasticsearch Chat Memory Repository
+
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+ org.springframework.ai
+ spring-ai-model-chat-memory-repository-elasticsearch
+ ${project.version}
+
+
+
+ org.springframework.ai
+ spring-ai-autoconfigure-model-chat-memory
+ ${project.parent.version}
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure-processor
+ true
+
+
+
+ co.elastic.clients
+ elasticsearch-java
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.ai
+ spring-ai-test
+ ${project.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+
+ org.testcontainers
+ elasticsearch
+ test
+
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
+
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryAutoConfiguration.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryAutoConfiguration.java
new file mode 100644
index 00000000000..528c1a77dd5
--- /dev/null
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryAutoConfiguration.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.model.chat.memory.repository.elasticsearch.autoconfigure;
+
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.json.jackson.JacksonJsonpMapper;
+import co.elastic.clients.transport.rest_client.RestClientTransport;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.elasticsearch.client.RestClient;
+
+import org.springframework.ai.chat.memory.repository.elasticsearch.ElasticSearchChatMemoryRepository;
+import org.springframework.ai.chat.memory.repository.elasticsearch.ElasticSearchChatMemoryRepositoryConfig;
+import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * {@link AutoConfiguration Auto-configuration} for
+ * {@link ElasticSearchChatMemoryRepository}.
+ *
+ * @author Fu Jian
+ * @since 1.1.0
+ */
+@AutoConfiguration(after = ElasticsearchRestClientAutoConfiguration.class, before = ChatMemoryAutoConfiguration.class)
+@ConditionalOnClass({ ElasticSearchChatMemoryRepository.class, RestClient.class })
+@EnableConfigurationProperties(ElasticSearchChatMemoryRepositoryProperties.class)
+public class ElasticSearchChatMemoryRepositoryAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ElasticSearchChatMemoryRepositoryConfig elasticSearchChatMemoryRepositoryConfig(
+ ElasticSearchChatMemoryRepositoryProperties properties, RestClient restClient) {
+ ElasticsearchClient elasticsearchClient = new ElasticsearchClient(new RestClientTransport(restClient,
+ new JacksonJsonpMapper(
+ new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false))))
+ .withTransportOptions(t -> t.addHeader("user-agent", "spring-ai-chat-memory elastic-java"));
+ return ElasticSearchChatMemoryRepositoryConfig.builder()
+ .withClient(elasticsearchClient)
+ .withIndexName(properties.getIndexName())
+ .build();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ElasticSearchChatMemoryRepository elasticSearchChatMemoryRepository(
+ ElasticSearchChatMemoryRepositoryConfig config) {
+ return ElasticSearchChatMemoryRepository.create(config);
+ }
+
+}
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryProperties.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryProperties.java
new file mode 100644
index 00000000000..1d8d6b58717
--- /dev/null
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryProperties.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.model.chat.memory.repository.elasticsearch.autoconfigure;
+
+import org.springframework.ai.chat.memory.repository.elasticsearch.ElasticSearchChatMemoryRepositoryConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Configuration properties for Elasticsearch chat memory.
+ *
+ * @author Fu Jian
+ * @since 1.1.0
+ */
+@ConfigurationProperties(ElasticSearchChatMemoryRepositoryProperties.CONFIG_PREFIX)
+public class ElasticSearchChatMemoryRepositoryProperties {
+
+ public static final String CONFIG_PREFIX = "spring.ai.chat.memory.repository.elasticsearch";
+
+ private String indexName = ElasticSearchChatMemoryRepositoryConfig.DEFAULT_INDEX_NAME;
+
+ public String getIndexName() {
+ return this.indexName;
+ }
+
+ public void setIndexName(String indexName) {
+ this.indexName = indexName;
+ }
+
+}
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000000..9921dcd20bf
--- /dev/null
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.springframework.ai.model.chat.memory.repository.elasticsearch.autoconfigure.ElasticSearchChatMemoryRepositoryAutoConfiguration
+
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/test/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/test/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryAutoConfigurationIT.java
new file mode 100644
index 00000000000..0e52b5224bc
--- /dev/null
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/test/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryAutoConfigurationIT.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.model.chat.memory.repository.elasticsearch.autoconfigure;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.Test;
+import org.testcontainers.elasticsearch.ElasticsearchContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import org.springframework.ai.chat.memory.repository.elasticsearch.ElasticSearchChatMemoryRepository;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.MessageType;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link ElasticSearchChatMemoryRepositoryAutoConfiguration}.
+ *
+ * @author Fu Jian
+ * @since 1.1.0
+ */
+@Testcontainers
+class ElasticSearchChatMemoryRepositoryAutoConfigurationIT {
+
+ @Container
+ static ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer(
+ "docker.elastic.co/elasticsearch/elasticsearch:8.10.2")
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("xpack.security.http.ssl.enabled", "false");
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class,
+ ElasticSearchChatMemoryRepositoryAutoConfiguration.class))
+ .withPropertyValues("spring.elasticsearch.uris=http://" + elasticsearchContainer.getHost() + ":"
+ + elasticsearchContainer.getMappedPort(9200))
+ .withPropertyValues("spring.ai.chat.memory.repository.elasticsearch.index-name=autoconfig-test-chat-memory");
+
+ @Test
+ void addAndGet() {
+ this.contextRunner.run(context -> {
+ ElasticSearchChatMemoryRepository memory = context.getBean(ElasticSearchChatMemoryRepository.class);
+
+ String conversationId = UUID.randomUUID().toString();
+ assertThat(memory.findByConversationId(conversationId)).isEmpty();
+
+ memory.saveAll(conversationId, List.of(new UserMessage("test question")));
+
+ assertThat(memory.findByConversationId(conversationId)).hasSize(1);
+ assertThat(memory.findByConversationId(conversationId).get(0).getMessageType()).isEqualTo(MessageType.USER);
+ assertThat(memory.findByConversationId(conversationId).get(0).getText()).isEqualTo("test question");
+
+ memory.deleteByConversationId(conversationId);
+ assertThat(memory.findByConversationId(conversationId)).isEmpty();
+
+ memory.saveAll(conversationId,
+ List.of(new UserMessage("test question"), new AssistantMessage("test answer")));
+
+ assertThat(memory.findByConversationId(conversationId)).hasSize(2);
+ assertThat(memory.findByConversationId(conversationId).get(0).getMessageType()).isEqualTo(MessageType.USER);
+ assertThat(memory.findByConversationId(conversationId).get(0).getText()).isEqualTo("test question");
+ assertThat(memory.findByConversationId(conversationId).get(1).getMessageType())
+ .isEqualTo(MessageType.ASSISTANT);
+ assertThat(memory.findByConversationId(conversationId).get(1).getText()).isEqualTo("test answer");
+ });
+ }
+
+ @Test
+ void propertiesConfiguration() {
+ this.contextRunner
+ .withPropertyValues("spring.ai.chat.memory.repository.elasticsearch.index-name=custom-testindex")
+ .run(context -> {
+ ElasticSearchChatMemoryRepositoryProperties properties = context
+ .getBean(ElasticSearchChatMemoryRepositoryProperties.class);
+ assertThat(properties.getIndexName()).isEqualTo("custom-testindex");
+ });
+ }
+
+ @Test
+ void findConversationIds() {
+ this.contextRunner.run(context -> {
+ ElasticSearchChatMemoryRepository memory = context.getBean(ElasticSearchChatMemoryRepository.class);
+
+ String conversationId1 = UUID.randomUUID().toString();
+ String conversationId2 = UUID.randomUUID().toString();
+
+ memory.saveAll(conversationId1, List.of(new UserMessage("test question 1")));
+ memory.saveAll(conversationId2, List.of(new UserMessage("test question 2")));
+
+ List conversationIds = memory.findConversationIds();
+ assertThat(conversationIds).contains(conversationId1, conversationId2);
+ });
+ }
+
+}
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/test/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryPropertiesTest.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/test/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryPropertiesTest.java
new file mode 100644
index 00000000000..8c10cc6a83b
--- /dev/null
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-elasticsearch/src/test/java/org/springframework/ai/model/chat/memory/repository/elasticsearch/autoconfigure/ElasticSearchChatMemoryRepositoryPropertiesTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.model.chat.memory.repository.elasticsearch.autoconfigure;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.ai.chat.memory.repository.elasticsearch.ElasticSearchChatMemoryRepositoryConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for {@link ElasticSearchChatMemoryRepositoryProperties}.
+ *
+ * @author Fu Jian
+ * @since 1.1.0
+ */
+class ElasticSearchChatMemoryRepositoryPropertiesTest {
+
+ @Test
+ void defaultValues() {
+ var props = new ElasticSearchChatMemoryRepositoryProperties();
+ assertThat(props.getIndexName()).isEqualTo(ElasticSearchChatMemoryRepositoryConfig.DEFAULT_INDEX_NAME);
+ }
+
+ @Test
+ void customValues() {
+ var props = new ElasticSearchChatMemoryRepositoryProperties();
+ props.setIndexName("custom_chat_memory");
+
+ assertThat(props.getIndexName()).isEqualTo("custom_chat_memory");
+ }
+
+}
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-elasticsearch/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-elasticsearch/pom.xml
new file mode 100644
index 00000000000..c9de07e0880
--- /dev/null
+++ b/memory/repository/spring-ai-model-chat-memory-repository-elasticsearch/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai-parent
+ 1.1.0-SNAPSHOT
+ ../../../pom.xml
+
+
+ spring-ai-model-chat-memory-repository-elasticsearch
+ Spring AI Elasticsearch Chat Memory Repository
+ Spring AI Elasticsearch Chat Memory Repository implementation
+
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+ org.springframework.ai
+ spring-ai-model
+ ${project.version}
+
+
+
+ co.elastic.clients
+ elasticsearch-java
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.ai
+ spring-ai-test
+ ${project.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+
+ org.testcontainers
+ elasticsearch
+ test
+
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
+
+
+
+
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/chat/memory/repository/elasticsearch/ElasticSearchChatMemoryRepository.java b/memory/repository/spring-ai-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/chat/memory/repository/elasticsearch/ElasticSearchChatMemoryRepository.java
new file mode 100644
index 00000000000..723f1330049
--- /dev/null
+++ b/memory/repository/spring-ai-model-chat-memory-repository-elasticsearch/src/main/java/org/springframework/ai/chat/memory/repository/elasticsearch/ElasticSearchChatMemoryRepository.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2023-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.chat.memory.repository.elasticsearch;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.SortOrder;
+import co.elastic.clients.elasticsearch.core.BulkRequest;
+import co.elastic.clients.elasticsearch.core.SearchResponse;
+import co.elastic.clients.elasticsearch.core.search.Hit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.ai.chat.memory.ChatMemoryRepository;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.MessageType;
+import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.chat.messages.ToolResponseMessage;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.util.Assert;
+
+/**
+ * An implementation of {@link ChatMemoryRepository} for Elasticsearch.
+ *
+ * @author Fu Jian
+ * @since 1.1.0
+ */
+public final class ElasticSearchChatMemoryRepository implements ChatMemoryRepository {
+
+ public static final String CONVERSATION_TS = ElasticSearchChatMemoryRepository.class.getSimpleName()
+ + "_message_timestamp";
+
+ private static final Logger logger = LoggerFactory.getLogger(ElasticSearchChatMemoryRepository.class);
+
+ private final ElasticsearchClient client;
+
+ private final String indexName;
+
+ private ElasticSearchChatMemoryRepository(ElasticSearchChatMemoryRepositoryConfig config) {
+ Assert.notNull(config, "config cannot be null");
+ this.client = config.getClient();
+ this.indexName = config.getIndexName();
+ }
+
+ public static ElasticSearchChatMemoryRepository create(ElasticSearchChatMemoryRepositoryConfig config) {
+ return new ElasticSearchChatMemoryRepository(config);
+ }
+
+ @Override
+ public List findConversationIds() {
+ logger.info("Finding all conversation IDs from Elasticsearch");
+
+ try {
+ SearchResponse