diff --git a/libraries/pom.xml b/libraries/pom.xml
index e539c0916f..b519b9cd53 100644
--- a/libraries/pom.xml
+++ b/libraries/pom.xml
@@ -590,11 +590,16 @@
docx4j
3.3.5
-
- javax.xml.bind
- jaxb-api
- 2.1
-
+
+ javax.xml.bind
+ jaxb-api
+ 2.1
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ ${caffeine.version}
+
@@ -666,5 +671,6 @@
1.0.3
1.0.0
3.8.4
+ 2.5.5
diff --git a/libraries/src/main/java/com/baeldung/caffeine/DataObject.java b/libraries/src/main/java/com/baeldung/caffeine/DataObject.java
new file mode 100644
index 0000000000..2a8b60b045
--- /dev/null
+++ b/libraries/src/main/java/com/baeldung/caffeine/DataObject.java
@@ -0,0 +1,28 @@
+package com.baeldung.caffeine;
+
+final class DataObject {
+ private final String data;
+
+ private static int objectCounter = 0;
+
+ private DataObject(String data) {
+ this.data = data;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ @Override
+ public String toString() {
+ return "DataObject{" +
+ "data='" + data + '\'' +
+ '}';
+ }
+
+ public static DataObject get(String data) {
+ objectCounter++;
+ System.out.println(String.format("Initializing DataObject#%d with data '%s'", objectCounter, data));
+ return new DataObject(data);
+ }
+}
diff --git a/libraries/src/test/java/com/baeldung/caffeine/CaffeineUnitTest.java b/libraries/src/test/java/com/baeldung/caffeine/CaffeineUnitTest.java
new file mode 100644
index 0000000000..56dbda5974
--- /dev/null
+++ b/libraries/src/test/java/com/baeldung/caffeine/CaffeineUnitTest.java
@@ -0,0 +1,174 @@
+package com.baeldung.caffeine;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+import org.junit.Test;
+
+import com.github.benmanes.caffeine.cache.*;
+
+public class CaffeineUnitTest {
+
+ @Test
+ public void givenCache_whenPopulate_thenValueStored() {
+ Cache cache = Caffeine.newBuilder()
+ .expireAfterWrite(1, TimeUnit.MINUTES)
+ .maximumSize(100)
+ .build();
+
+ String key = "A";
+ DataObject dataObject = cache.getIfPresent(key);
+
+ assertNull(dataObject);
+
+ dataObject = cache.get(key, k -> DataObject.get("Data for A"));
+
+ assertNotNull(dataObject);
+ assertEquals("Data for A", dataObject.getData());
+
+ cache.put(key, dataObject);
+ dataObject = cache.getIfPresent(key);
+
+ assertNotNull(dataObject);
+
+ cache.invalidate(key);
+ dataObject = cache.getIfPresent(key);
+
+ assertNull(dataObject);
+ }
+
+ @Test
+ public void givenLoadingCache_whenGet_thenValuePopulated() {
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(100)
+ .expireAfterWrite(1, TimeUnit.MINUTES)
+ .build(k -> DataObject.get("Data for " + k));
+ String key = "A";
+
+ DataObject dataObject = cache.get(key);
+
+ assertNotNull(dataObject);
+ assertEquals("Data for " + key, dataObject.getData());
+
+ Map dataObjectMap = cache.getAll(Arrays.asList("A", "B", "C"));
+
+ assertEquals(3, dataObjectMap.size());
+ }
+
+ @Test
+ public void givenAsyncLoadingCache_whenGet_thenValuePopulated() {
+
+ AsyncLoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(100)
+ .expireAfterWrite(1, TimeUnit.MINUTES)
+ .buildAsync(k -> DataObject.get("Data for " + k));
+ String key = "A";
+
+ cache.get(key).thenAccept(dataObject -> {
+ assertNotNull(dataObject);
+ assertEquals("Data for " + key, dataObject.getData());
+ });
+
+ cache.getAll(Arrays.asList("A", "B", "C"))
+ .thenAccept(dataObjectMap -> assertEquals(3, dataObjectMap.size()));
+ }
+
+ @Test
+ public void givenLoadingCacheWithSmallSize_whenPut_thenSizeIsConstant() {
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(1)
+ .refreshAfterWrite(10, TimeUnit.MINUTES)
+ .build(k -> DataObject.get("Data for " + k));
+
+ assertEquals(0, cache.estimatedSize());
+
+ cache.get("A");
+
+ assertEquals(1, cache.estimatedSize());
+
+ cache.get("B");
+ cache.cleanUp();
+
+ assertEquals(1, cache.estimatedSize());
+ }
+
+ @Test
+ public void givenLoadingCacheWithWeigher_whenPut_thenSizeIsConstant() {
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumWeight(10)
+ .weigher((k,v) -> 5)
+ .build(k -> DataObject.get("Data for " + k));
+
+ assertEquals(0, cache.estimatedSize());
+
+ cache.get("A");
+
+ assertEquals(1, cache.estimatedSize());
+
+ cache.get("B");
+
+ assertEquals(2, cache.estimatedSize());
+
+ cache.get("C");
+ cache.cleanUp();
+
+ assertEquals(2, cache.estimatedSize());
+ }
+
+ @Test
+ public void givenTimeEvictionCache_whenTimeLeft_thenValueEvicted() {
+ LoadingCache cache = Caffeine.newBuilder()
+ .expireAfterAccess(5, TimeUnit.MINUTES)
+ .build(k -> DataObject.get("Data for " + k));
+
+ cache = Caffeine.newBuilder()
+ .expireAfterWrite(10, TimeUnit.SECONDS)
+ .weakKeys()
+ .weakValues()
+ .build(k -> DataObject.get("Data for " + k));
+
+ cache = Caffeine.newBuilder()
+ .expireAfterWrite(10, TimeUnit.SECONDS)
+ .softValues()
+ .build(k -> DataObject.get("Data for " + k));
+
+ cache = Caffeine.newBuilder().expireAfter(new Expiry() {
+ @Override
+ public long expireAfterCreate(@Nonnull String key, @Nonnull DataObject value, long currentTime) {
+ return value.getData().length() * 1000;
+ }
+
+ @Override
+ public long expireAfterUpdate(@Nonnull String key, @Nonnull DataObject value, long currentTime, long currentDuration) {
+ return currentDuration;
+ }
+
+ @Override
+ public long expireAfterRead(@Nonnull String key, @Nonnull DataObject value, long currentTime, long currentDuration) {
+ return currentDuration;
+ }
+ }).build(k -> DataObject.get("Data for " + k));
+
+ cache = Caffeine.newBuilder()
+ .refreshAfterWrite(1, TimeUnit.MINUTES)
+ .build(k -> DataObject.get("Data for " + k));
+ }
+
+ @Test
+ public void givenCache_whenStatsEnabled_thenStatsRecorded() {
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(100)
+ .recordStats()
+ .build(k -> DataObject.get("Data for " + k));
+ cache.get("A");
+ cache.get("A");
+
+ assertEquals(1, cache.stats().hitCount());
+ assertEquals(1, cache.stats().missCount());
+ }
+}
\ No newline at end of file