Guava compatibility adapter for Caffeine caching library, enabling seamless migration from Guava caches to Caffeine's high-performance caching through familiar Guava Cache API interfaces.
npx @tessl/cli install tessl/maven-com-github-ben-manes-caffeine--guava@3.2.0Caffeine Guava provides a compatibility adapter that enables seamless migration from Guava caches to Caffeine's high-performance caching library. The adapter wraps Caffeine cache instances in familiar Guava Cache API interfaces, allowing applications to benefit from Caffeine's superior performance characteristics (TinyLFU admission policy, advanced eviction strategies, and optimized concurrent access) while maintaining full API compatibility with existing Guava cache code.
implementation 'com.github.ben-manes.caffeine:guava:3.2.0'com.github.ben-manes.caffeine:guavaimport com.github.benmanes.caffeine.guava.CaffeinatedGuava;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.cache.Cache;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.CacheLoader;
// For static factory methods
import java.util.function.Function;
import java.util.concurrent.Executor;import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.cache.Cache;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.CacheLoader;
import java.time.Duration;
// Create a simple Guava-compatible cache from Caffeine
Cache<String, String> cache = CaffeinatedGuava.build(
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(10))
);
cache.put("key", "value");
String value = cache.getIfPresent("key");
// Create a loading cache with Guava CacheLoader
LoadingCache<String, String> loadingCache = CaffeinatedGuava.build(
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(Duration.ofMinutes(5)),
new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return loadFromDatabase(key);
}
}
);
String loadedValue = loadingCache.get("key");The Caffeine Guava adapter is built around several key components:
Cache interface while delegating to a Caffeine cache instanceCaffeinatedGuavaCache that implements Guava's LoadingCache interfaceCreates Guava Cache facades around Caffeine cache instances.
/**
* Returns a Caffeine cache wrapped in a Guava Cache facade.
* @param builder the configured cache builder
* @param <K> the most general key type to create caches for
* @param <V> the most general value type to create caches for
* @param <K1> the key type of the cache
* @param <V1> the value type of the cache
* @return a cache exposed under the Guava APIs
*/
public static <K, V, K1 extends K, V1 extends V> Cache<K1, V1> build(Caffeine<K, V> builder);Usage Example:
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
import com.google.common.cache.Cache;
import java.time.Duration;
Cache<String, Integer> cache = CaffeinatedGuava.build(
Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(Duration.ofHours(1))
.recordStats()
);
// Use as a normal Guava Cache
cache.put("count", 42);
Integer count = cache.getIfPresent("count");
cache.invalidate("count");Creates Guava LoadingCache facades around Caffeine cache instances using Guava CacheLoader.
/**
* Returns a Caffeine cache wrapped in a Guava LoadingCache facade.
* @param builder the configured cache builder
* @param loader the cache loader used to obtain new values
* @param <K> the most general key type to create caches for
* @param <V> the most general value type to create caches for
* @param <K1> the key type of the cache
* @param <V1> the value type of the cache
* @return a cache exposed under the Guava APIs
*/
public static <K, V, K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
Caffeine<K, V> builder,
CacheLoader<? super K1, V1> loader
);Usage Example:
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.Map;
import java.util.Arrays;
LoadingCache<String, String> userCache = CaffeinatedGuava.build(
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(Duration.ofMinutes(30)),
new CacheLoader<String, String>() {
@Override
public String load(String userId) throws Exception {
return fetchUserFromDatabase(userId);
}
@Override
public Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {
return fetchUsersFromDatabase(keys);
}
}
);
try {
String user = userCache.get("user123");
Map<String, String> users = userCache.getAll(Arrays.asList("user1", "user2"));
} catch (ExecutionException e) {
// Handle loading exceptions
}Creates Guava LoadingCache facades around Caffeine cache instances using Caffeine CacheLoader.
/**
* Returns a Caffeine cache wrapped in a Guava LoadingCache facade.
* @param builder the configured cache builder
* @param loader the cache loader used to obtain new values
* @param <K> the most general key type to create caches for
* @param <V> the most general value type to create caches for
* @param <K1> the key type of the cache
* @param <V1> the value type of the cache
* @return a cache exposed under the Guava APIs
*/
public static <K, V, K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
Caffeine<K, V> builder,
com.github.benmanes.caffeine.cache.CacheLoader<? super K1, V1> loader
);Usage Example:
import com.github.benmanes.caffeine.cache.CacheLoader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
LoadingCache<String, String> configCache = CaffeinatedGuava.build(
Caffeine.newBuilder()
.refreshAfterWrite(Duration.ofMinutes(5))
.maximumSize(500),
new com.github.benmanes.caffeine.cache.CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return loadConfigValue(key);
}
@Override
public CompletableFuture<String> asyncReload(String key, String oldValue, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
try {
return loadConfigValue(key);
} catch (Exception e) {
throw new RuntimeException(e);
}
}, executor);
}
}
);Converts Guava CacheLoader to Caffeine CacheLoader for direct use with Caffeine APIs.
/**
* Returns a Caffeine cache loader that delegates to a Guava cache loader.
* @param loader the cache loader used to obtain new values
* @param <K> the type of keys
* @param <V> the type of values
* @return a cache loader exposed under the Caffeine APIs
*/
public static <K, V> com.github.benmanes.caffeine.cache.CacheLoader<K, V> caffeinate(
CacheLoader<K, V> loader
);Usage Example:
// Existing Guava CacheLoader
CacheLoader<String, String> guavaLoader = new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return expensiveOperation(key);
}
};
// Convert to Caffeine CacheLoader
com.github.benmanes.caffeine.cache.CacheLoader<String, String> caffeineLoader =
CaffeinatedGuava.caffeinate(guavaLoader);
// Use with native Caffeine cache
com.github.benmanes.caffeine.cache.LoadingCache<String, String> nativeCache =
Caffeine.newBuilder()
.maximumSize(1000)
.build(caffeineLoader);Creates CacheLoader instances using static factory methods for common patterns.
Creates a CacheLoader from a Function for simple loading scenarios.
/**
* Returns a cache loader that uses the provided function for loading values.
* @param function the function to use for loading values
* @param <K> the type of keys
* @param <V> the type of values
* @return a cache loader that delegates to the function
*/
public static <K, V> CacheLoader<K, V> from(Function<K, V> function);Usage Example:
import com.google.common.cache.CacheLoader;
import java.util.function.Function;
// Create a simple loader using a lambda
CacheLoader<String, String> loader = CacheLoader.from(key -> "value-for-" + key);
// Use with cache creation
LoadingCache<String, String> cache = CaffeinatedGuava.build(
Caffeine.newBuilder().maximumSize(1000),
loader
);
String value = cache.get("mykey"); // Returns "value-for-mykey"Wraps an existing CacheLoader to perform asynchronous reloads in the background.
/**
* Returns a cache loader that performs asynchronous reloads.
* @param loader the cache loader to wrap
* @param executor the executor to use for async operations
* @param <K> the type of keys
* @param <V> the type of values
* @return a cache loader that performs async reloads
*/
public static <K, V> CacheLoader<K, V> asyncReloading(
CacheLoader<K, V> loader,
Executor executor
);Usage Example:
import com.google.common.cache.CacheLoader;
import java.util.concurrent.Executors;
import java.util.concurrent.Executor;
// Original synchronous loader
CacheLoader<String, String> syncLoader = new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return fetchFromDatabase(key); // Slow operation
}
};
// Wrap for asynchronous reloading
Executor executor = Executors.newFixedThreadPool(5);
CacheLoader<String, String> asyncLoader = CacheLoader.asyncReloading(syncLoader, executor);
// Use with refresh-enabled cache
LoadingCache<String, String> cache = CaffeinatedGuava.build(
Caffeine.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(Duration.ofMinutes(5)), // Triggers async reload
asyncLoader
);The adapter implements the complete Guava Cache interface:
public interface Cache<K, V> {
V getIfPresent(Object key);
V get(K key, Callable<? extends V> valueLoader) throws ExecutionException;
ImmutableMap<K, V> getAllPresent(Iterable<?> keys);
void put(K key, V value);
void putAll(Map<? extends K, ? extends V> m);
void invalidate(Object key);
void invalidateAll(Iterable<?> keys);
void invalidateAll();
long size();
CacheStats stats();
ConcurrentMap<K, V> asMap();
void cleanUp();
}The adapter implements the complete Guava LoadingCache interface:
public interface LoadingCache<K, V> extends Cache<K, V>, Function<K, V> {
V get(K key) throws ExecutionException;
V getUnchecked(K key);
ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException;
V apply(K key); // Deprecated
void refresh(K key);
}Standard Guava CacheLoader that can be used with the adapter:
public abstract class CacheLoader<K, V> {
public abstract V load(K key) throws Exception;
public Map<K, V> loadAll(Iterable<? extends K> keys) throws Exception;
public ListenableFuture<V> reload(K key, V oldValue) throws Exception;
// Static factory methods
public static <K, V> CacheLoader<K, V> from(Function<K, V> function);
public static <K, V> CacheLoader<K, V> asyncReloading(
CacheLoader<K, V> loader, Executor executor);
}The adapter handles and translates various exception types:
// Thrown when a cache loader returns null
public class InvalidCacheLoadException extends RuntimeException;
// Wrapper for checked exceptions during cache loading
public class ExecutionException extends Exception;
// Wrapper for runtime exceptions during cache loading
public class UncheckedExecutionException extends RuntimeException;
// Wrapper for errors during cache loading
public class ExecutionError extends Error;The Caffeine Guava adapter provides comprehensive exception translation between Caffeine and Guava APIs:
InvalidCacheLoadException is thrown when cache loaders return null valuesExecutionException for methods that declare itUncheckedExecutionException for unchecked methodsExecutionError to maintain proper error propagationExample Error Handling:
import java.util.concurrent.ExecutionException;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
try {
String value = loadingCache.get("key");
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof MyCustomException) {
// Handle specific exception
}
} catch (InvalidCacheLoadException e) {
// Handle null value from loader
}