diff --git a/pom.xml b/pom.xml
index f2a53f38b7..9a34ed0afe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,6 +7,9 @@
com.baeldung
parent-modules
1.0.0-SNAPSHOT
+
+ spring-webflux-caching
+
parent-modules
pom
diff --git a/spring-webflux-caching/pom.xml b/spring-webflux-caching/pom.xml
new file mode 100644
index 0000000000..ed9800bce9
--- /dev/null
+++ b/spring-webflux-caching/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+ com.baeldung.spring
+ spring-webflux-caching
+ 1.0.0-SNAPSHOT
+ spring-webflux-caching
+ jar
+ Spring WebFlux Caching Sample
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../parent-boot-2
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ io.reactivex.rxjava2
+ rxjava
+ 2.2.19
+
+
+ io.projectreactor.addons
+ reactor-extra
+ 3.4.5
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 3.0.4
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.testcontainers
+ mongodb
+ 1.16.2
+ test
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflux-caching/src/main/java/com/baeldung/caching/Item.java b/spring-webflux-caching/src/main/java/com/baeldung/caching/Item.java
new file mode 100644
index 0000000000..127975b0e7
--- /dev/null
+++ b/spring-webflux-caching/src/main/java/com/baeldung/caching/Item.java
@@ -0,0 +1,50 @@
+package com.baeldung.caching;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Document
+public class Item {
+
+ @Id
+ private String _id;
+ private String name;
+ private double price;
+
+ public Item(String name, double price) {
+ this.name = name;
+ this.price = price;
+ }
+
+ public Item() {
+ }
+
+ public String get_id() {
+ return _id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+ @Override
+ public String toString() {
+ return "Item{" +
+ "id='" + _id + '\'' +
+ ", name='" + name + '\'' +
+ ", price=" + price +
+ '}';
+ }
+}
diff --git a/spring-webflux-caching/src/main/java/com/baeldung/caching/ItemRepository.java b/spring-webflux-caching/src/main/java/com/baeldung/caching/ItemRepository.java
new file mode 100644
index 0000000000..a76489623e
--- /dev/null
+++ b/spring-webflux-caching/src/main/java/com/baeldung/caching/ItemRepository.java
@@ -0,0 +1,8 @@
+package com.baeldung.caching;
+
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ItemRepository extends ReactiveMongoRepository- {
+}
diff --git a/spring-webflux-caching/src/main/java/com/baeldung/caching/ItemService.java b/spring-webflux-caching/src/main/java/com/baeldung/caching/ItemService.java
new file mode 100644
index 0000000000..9dc9ba1642
--- /dev/null
+++ b/spring-webflux-caching/src/main/java/com/baeldung/caching/ItemService.java
@@ -0,0 +1,42 @@
+package com.baeldung.caching;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import reactor.cache.CacheMono;
+import reactor.core.publisher.Mono;
+
+@Service
+public class ItemService {
+
+ private final ItemRepository repository;
+ private final LoadingCache cache;
+
+ public ItemService(ItemRepository repository) {
+ this.repository = repository;
+ this.cache = Caffeine.newBuilder()
+ .build(this::getItem_withAddons);
+ }
+
+ @Cacheable("items")
+ public Mono
- getItem(String id) {
+ return repository.findById(id);
+ }
+
+ public Mono
- save(Item item) {
+ return repository.save(item);
+ }
+
+ @Cacheable("items")
+ public Mono
- getItem_withCache(String id) {
+ return repository.findById(id).cache();
+ }
+
+ @Cacheable("items")
+ public Mono
- getItem_withAddons(String id) {
+ return CacheMono.lookup(cache.asMap(), id)
+ .onCacheMissResume(() -> repository.findById(id).cast(Object.class)).cast(Item.class);
+ }
+
+}
diff --git a/spring-webflux-caching/src/main/java/com/baeldung/caching/SpringWebfluxCachingApplication.java b/spring-webflux-caching/src/main/java/com/baeldung/caching/SpringWebfluxCachingApplication.java
new file mode 100644
index 0000000000..7331576bd5
--- /dev/null
+++ b/spring-webflux-caching/src/main/java/com/baeldung/caching/SpringWebfluxCachingApplication.java
@@ -0,0 +1,16 @@
+package com.baeldung.caching;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+
+@SpringBootApplication
+@EnableMongoRepositories
+@EnableCaching
+public class SpringWebfluxCachingApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringWebfluxCachingApplication.class, args);
+ }
+}
diff --git a/spring-webflux-caching/src/main/resources/application.properties b/spring-webflux-caching/src/main/resources/application.properties
new file mode 100644
index 0000000000..23414da2dd
--- /dev/null
+++ b/spring-webflux-caching/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=DEBUG
+logging.level.org.springframework.cache=TRACE
\ No newline at end of file
diff --git a/spring-webflux-caching/src/test/java/com/baeldung/caching/MonoFluxResultCachingLiveTest.java b/spring-webflux-caching/src/test/java/com/baeldung/caching/MonoFluxResultCachingLiveTest.java
new file mode 100644
index 0000000000..bf96b35dcb
--- /dev/null
+++ b/spring-webflux-caching/src/test/java/com/baeldung/caching/MonoFluxResultCachingLiveTest.java
@@ -0,0 +1,95 @@
+package com.baeldung.caching;
+
+
+import org.assertj.core.api.Assertions;
+import org.junit.ClassRule;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.utility.DockerImageName;
+import reactor.core.publisher.Mono;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+public class MonoFluxResultCachingLiveTest {
+
+
+ @Autowired
+ ItemService itemService;
+
+ final static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
+
+ @DynamicPropertySource
+ static void mongoDbProperties(DynamicPropertyRegistry registry) {
+ mongoDBContainer.start();
+ registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
+ }
+
+@Test
+public void givenItem_whenGetItemIsCalled_thenMonoIsCached() {
+ Mono
- glass = itemService.save(new Item("glass", 1.00));
+
+ String id = glass.block().get_id();
+
+ Mono
- mono = itemService.getItem(id);
+ Item item = mono.block();
+
+ assertThat(item).isNotNull();
+ assertThat(item.getName()).isEqualTo("glass");
+ assertThat(item.getPrice()).isEqualTo(1.00);
+
+ Mono
- mono2 = itemService.getItem(id);
+ Item item2 = mono2.block();
+
+ assertThat(item2).isNotNull();
+ assertThat(item2.getName()).isEqualTo("glass");
+ assertThat(item2.getPrice()).isEqualTo(1.00);
+}
+
+ @Test
+ public void givenItem_whenGetItemWithCacheIsCalled_thenMonoResultIsCached() {
+ Mono
- glass = itemService.save(new Item("glass", 1.00));
+
+ String id = glass.block().get_id();
+
+ Mono
- mono = itemService.getItem_withCache(id);
+ Item item = mono.block();
+
+ assertThat(item).isNotNull();
+ assertThat(item.getName()).isEqualTo("glass");
+ assertThat(item.getPrice()).isEqualTo(1.00);
+
+ Mono
- mono2 = itemService.getItem_withCache(id);
+ Item item2 = mono2.block();
+
+ assertThat(item2).isNotNull();
+ assertThat(item2.getName()).isEqualTo("glass");
+ assertThat(item2.getPrice()).isEqualTo(1.00);
+ }
+
+ @Test
+ public void givenItem_whenGetItemWithAddonsIsCalled_thenMonoResultIsCached() {
+ Mono
- glass = itemService.save(new Item("glass", 1.00));
+
+ String id = glass.block().get_id();
+
+ Mono
- mono = itemService.getItem_withAddons(id);
+ Item item = mono.block();
+
+ assertThat(item).isNotNull();
+ assertThat(item.getName()).isEqualTo("glass");
+ assertThat(item.getPrice()).isEqualTo(1.00);
+
+ Mono
- mono2 = itemService.getItem_withAddons(id);
+ Item item2 = mono2.block();
+
+ assertThat(item2).isNotNull();
+ assertThat(item2.getName()).isEqualTo("glass");
+ assertThat(item2.getPrice()).isEqualTo(1.00);
+ }
+
+}