diff --git a/spring-ai-modules/pom.xml b/spring-ai-modules/pom.xml
index 17649e22f089..6f8d95626a39 100644
--- a/spring-ai-modules/pom.xml
+++ b/spring-ai-modules/pom.xml
@@ -20,6 +20,7 @@
spring-ai-introduction
spring-ai-mcp
spring-ai-multiple-llms
+ spring-ai-semantic-caching
spring-ai-text-to-sql
spring-ai-vector-stores
spring-ai-agentic-patterns
diff --git a/spring-ai-modules/spring-ai-semantic-caching/pom.xml b/spring-ai-modules/spring-ai-semantic-caching/pom.xml
new file mode 100644
index 000000000000..ee56ecf5ff7d
--- /dev/null
+++ b/spring-ai-modules/spring-ai-semantic-caching/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+
+
+ com.baeldung
+ spring-ai-modules
+ 0.0.1
+ ../pom.xml
+
+
+ spring-ai-semantic-caching
+ 0.0.1
+ spring-ai-semantic-caching
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.ai
+ spring-ai-starter-model-openai
+
+
+ org.springframework.ai
+ spring-ai-starter-vector-store-redis
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ 21
+ 1.0.3
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-bom
+ ${spring-ai.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/Application.java b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/Application.java
new file mode 100644
index 000000000000..6cfd1cf22d55
--- /dev/null
+++ b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/Application.java
@@ -0,0 +1,13 @@
+package com.baeldung.semantic.cache;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/LLMConfiguration.java b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/LLMConfiguration.java
new file mode 100644
index 000000000000..ce04cd3a0025
--- /dev/null
+++ b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/LLMConfiguration.java
@@ -0,0 +1,35 @@
+package com.baeldung.semantic.cache;
+
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.vectorstore.redis.RedisVectorStore;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import redis.clients.jedis.JedisPooled;
+
+@Configuration
+@EnableConfigurationProperties(SemanticCacheProperties.class)
+class LLMConfiguration {
+
+ @Bean
+ JedisPooled jedisPooled(RedisProperties redisProperties) {
+ return new JedisPooled(redisProperties.getUrl());
+ }
+
+ @Bean
+ RedisVectorStore vectorStore(
+ JedisPooled jedisPooled,
+ EmbeddingModel embeddingModel,
+ SemanticCacheProperties semanticCacheProperties
+ ) {
+ return RedisVectorStore
+ .builder(jedisPooled, embeddingModel)
+ .contentFieldName(semanticCacheProperties.contentField())
+ .embeddingFieldName(semanticCacheProperties.embeddingField())
+ .metadataFields(
+ RedisVectorStore.MetadataField.text(semanticCacheProperties.metadataField()))
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/SemanticCacheProperties.java b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/SemanticCacheProperties.java
new file mode 100644
index 000000000000..23b0fc73670d
--- /dev/null
+++ b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/SemanticCacheProperties.java
@@ -0,0 +1,11 @@
+package com.baeldung.semantic.cache;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "com.baeldung.semantic.cache")
+record SemanticCacheProperties(
+ Double similarityThreshold,
+ String contentField,
+ String embeddingField,
+ String metadataField
+) {}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/SemanticCachingService.java b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/SemanticCachingService.java
new file mode 100644
index 000000000000..a2ae7aa16f53
--- /dev/null
+++ b/spring-ai-modules/spring-ai-semantic-caching/src/main/java/com/baeldung/semantic/cache/SemanticCachingService.java
@@ -0,0 +1,51 @@
+package com.baeldung.semantic.cache;
+
+import org.springframework.ai.document.Document;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@EnableConfigurationProperties(SemanticCacheProperties.class)
+class SemanticCachingService {
+
+ private final VectorStore vectorStore;
+ private final SemanticCacheProperties semanticCacheProperties;
+
+ SemanticCachingService(VectorStore vectorStore, SemanticCacheProperties semanticCacheProperties) {
+ this.vectorStore = vectorStore;
+ this.semanticCacheProperties = semanticCacheProperties;
+ }
+
+ void save(String question, String answer) {
+ Document document = Document
+ .builder()
+ .text(question)
+ .metadata(semanticCacheProperties.metadataField(), answer)
+ .build();
+ vectorStore.add(List.of(document));
+ }
+
+ Optional search(String question) {
+ SearchRequest searchRequest = SearchRequest.builder()
+ .query(question)
+ .similarityThreshold(semanticCacheProperties.similarityThreshold())
+ .topK(1)
+ .build();
+ List results = vectorStore.similaritySearch(searchRequest);
+
+ if (results.isEmpty()) {
+ return Optional.empty();
+ }
+
+ Document result = results.getFirst();
+ return Optional
+ .ofNullable(result.getMetadata().get(semanticCacheProperties.metadataField()))
+ .map(String::valueOf);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-semantic-caching/src/main/resources/application.yaml b/spring-ai-modules/spring-ai-semantic-caching/src/main/resources/application.yaml
new file mode 100644
index 000000000000..3e513b525e93
--- /dev/null
+++ b/spring-ai-modules/spring-ai-semantic-caching/src/main/resources/application.yaml
@@ -0,0 +1,20 @@
+spring:
+ ai:
+ openai:
+ api-key: ${OPENAI_API_KEY}
+ embedding:
+ options:
+ model: text-embedding-3-small
+ dimensions: 512
+ data:
+ redis:
+ url: ${REDIS_URL}
+
+com:
+ baeldung:
+ semantic:
+ cache:
+ similarity-threshold: 0.8
+ content-field: question
+ embedding-field: embedding
+ metadata-field: answer
\ No newline at end of file
diff --git a/spring-ai-modules/spring-ai-semantic-caching/src/test/java/com/baeldung/semantic/cache/SemanticCacheLiveTest.java b/spring-ai-modules/spring-ai-semantic-caching/src/test/java/com/baeldung/semantic/cache/SemanticCacheLiveTest.java
new file mode 100644
index 000000000000..bd313c0bbdb1
--- /dev/null
+++ b/spring-ai-modules/spring-ai-semantic-caching/src/test/java/com/baeldung/semantic/cache/SemanticCacheLiveTest.java
@@ -0,0 +1,37 @@
+package com.baeldung.semantic.cache;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+@SpringBootTest
+@EnabledIfEnvironmentVariables({
+ @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*"),
+ @EnabledIfEnvironmentVariable(named = "REDIS_URL", matches = ".*")
+})
+class SemanticCacheLiveTest {
+
+ @Autowired
+ private SemanticCachingService semanticCachingService;
+
+ @Test
+ void whenUsingSemanticCache_thenCacheReturnsAnswerForSemanticallyRelatedQuestion() {
+ String question = "How many sick leaves can I take?";
+ String answer = "No leaves allowed! Get back to work!!";
+ semanticCachingService.save(question, answer);
+
+ String rephrasedQuestion = "How many days sick leave can I take?";
+ assertThat(semanticCachingService.search(rephrasedQuestion))
+ .isPresent()
+ .hasValue(answer);
+
+ String unrelatedQuestion = "Can I get a raise?";
+ assertThat(semanticCachingService.search(unrelatedQuestion))
+ .isEmpty();
+ }
+
+}
\ No newline at end of file