# How to Implement Custom Guava Caches: A Complete Guide to CacheBuilder Extensions

> Implement custom Guava caches by extending CacheLoader, Weigher, and RemovalListener. Build specialized LoadingCache instances with CacheBuilder for efficient data management.

- Repository: [Google/guava](https://github.com/google/guava)
- Tags: how-to-guide
- Published: 2026-08-10

---

**Implement custom Guava caches by extending `CacheLoader` for loading logic, implementing `Weigher` for size-based eviction, providing `RemovalListener` for eviction callbacks, and assembling the configuration via `CacheBuilder` to create specialized `LoadingCache` instances.**

The Google Guava library (`google/guava`) provides a robust caching framework centered on the `CacheBuilder` and `CacheLoader` abstractions found in the `com.google.common.cache` package. To implement custom Guava caches with specialized data loading, eviction strategies, or monitoring capabilities, developers utilize specific extension points defined in the source code. This guide demonstrates how to leverage these APIs—referencing actual file paths and method signatures from the Guava repository—to build production-ready caching solutions.

## Customizing Data Loading with CacheLoader

The primary mechanism for defining how missing values are computed or retrieved is the **`CacheLoader`** abstract class located in [`guava/src/com/google/common/cache/CacheLoader.java`](https://github.com/google/guava/blob/main/guava/src/com/google/common/cache/CacheLoader.java). Subclasses implement the **`load(K key)`** method to define synchronous loading logic, and optionally override **`loadAll(Iterable<? extends K> keys)`** for batch retrieval.

For caches requiring automatic loading, pass your `CacheLoader` implementation to `CacheBuilder.build()`:

```java
import com.google.common.cache.*;

public class UserCache {
    private final LoadingCache<String, User> cache;

    public UserCache() {
        cache = CacheBuilder.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(15, java.util.concurrent.TimeUnit.MINUTES)
            .recordStats()
            .build(new CacheLoader<String, User>() {
                @Override
                public User load(String userId) throws Exception {
                    return fetchUserFromDataSource(userId);
                }
            });
    }

    public User get(String userId) throws ExecutionException {
        return cache.get(userId);
    }

    private User fetchUserFromDataSource(String userId) {
        return new User(userId, "Name-" + userId);
    }
}

```

## Implementing Weight-Based Eviction with Weigher

When entries vary significantly in memory footprint, use the **`Weigher<K,V>`** interface to define custom weights instead of simple entry counts. Configure this in [`guava/src/com/google/common/cache/CacheBuilder.java`](https://github.com/google/guava/blob/main/guava/src/com/google/common/cache/CacheBuilder.java) via **`weigher(Weigher<? super K, ? super V> weigher)`**, paired with **`maximumWeight(long maximumWeight)`**.

The weigher calculates an integer weight for each entry. Eviction occurs when the total weight exceeds the configured maximum:

```java
import com.google.common.cache.*;

public class WeightedCache {
    private final Cache<String, byte[]> cache;

    public WeightedCache() {
        cache = CacheBuilder.newBuilder()
            .maximumWeight(100_000_000) // 100 MB total weight
            .weigher((String key, byte[] value) -> value.length)
            .removalListener((RemovalNotification<String, byte[]> notification) -> {
                System.out.println("Evicted key: " + notification.getKey()
                                   + " because of " + notification.getCause());
            })
            .build();
    }

    public void put(String key, byte[] data) {
        cache.put(key, data);
    }

    public byte[] getIfPresent(String key) {
        return cache.getIfPresent(key);
    }
}

```

## Handling Removal Events with RemovalListener

To react when entries are evicted, expired, or explicitly removed, provide a **`RemovalListener<K,V>`** via `CacheBuilder.removalListener()`. The listener receives a **`RemovalNotification<K,V>`** object containing the key, value, and **`RemovalCause`**.

This hook is essential for resource cleanup, logging, or triggering downstream updates when cache entries disappear. Register the listener during cache construction to capture all removal events.

## Configuring Asynchronous Refresh Policies

For entries that should be periodically refreshed without blocking readers, override **`reload(K key, V oldValue)`** in your `CacheLoader`. Enable this behavior with **`refreshAfterWrite(long duration, TimeUnit unit)`** in the builder.

The `reload()` method returns a **`ListenableFuture<V>`**, allowing asynchronous computation using Guava's concurrency utilities:

```java
import com.google.common.cache.*;

public class RefreshingCache {
    private final LoadingCache<Integer, ComputationResult> cache;

    public RefreshingCache() {
        cache = CacheBuilder.newBuilder()
            .refreshAfterWrite(5, java.util.concurrent.TimeUnit.MINUTES)
            .build(new CacheLoader<Integer, ComputationResult>() {
                @Override
                public ComputationResult load(Integer key) {
                    return computeExpensiveResult(key);
                }

                @Override
                public ListenableFuture<ComputationResult> reload(
                        Integer key, ComputationResult oldValue) {
                    return Futures.immediateFuture(computeExpensiveResult(key));
                }
            });
    }

    private ComputationResult computeExpensiveResult(Integer key) {
        return new ComputationResult(key, Math.sqrt(key));
    }
}

```

## Monitoring Cache Statistics

Enable performance tracking by calling **`recordStats()`** on the `CacheBuilder`. Query the resulting **`CacheStats`** object (defined in [`guava/src/com/google/common/cache/CacheStats.java`](https://github.com/google/guava/blob/main/guava/src/com/google/common/cache/CacheStats.java)) via **`Cache.stats()`** to access hit rates, eviction counts, and load latencies.

This immutable snapshot provides visibility into cache effectiveness and helps tune maximum size or expiration parameters.

## Advanced: Custom Cache Implementations

For scenarios requiring behavior not supported by `CacheBuilder`, implement the **`Cache<K,V>`** interface directly or extend Guava's internal **`AbstractCache`**. This approach, defined in [`guava/src/com/google/common/cache/Cache.java`](https://github.com/google/guava/blob/main/guava/src/com/google/common/cache/Cache.java), requires manual implementation of **`getIfPresent`**, **`put`**, **`invalidate`**, and other operations. Most production use cases, however, satisfy requirements through the standard builder and loader extension points.

## Summary

- **Extend `CacheLoader`** to define custom loading logic for cache misses, implementing `load()` and optionally `loadAll()` or `reload()`.
- **Implement `Weigher`** to control eviction based on entry weight rather than count, using `CacheBuilder.weigher()` and `maximumWeight()`.
- **Provide `RemovalListener`** to execute callbacks when entries are evicted, expired, or removed explicitly.
- **Override `reload()`** in `CacheLoader` and enable `refreshAfterWrite()` for non-blocking asynchronous refresh of stale entries.
- **Call `recordStats()`** to expose `CacheStats` for monitoring hit rates and performance metrics.
- **Reference source files** including [`CacheBuilder.java`](https://github.com/google/guava/blob/main/CacheBuilder.java), [`CacheLoader.java`](https://github.com/google/guava/blob/main/CacheLoader.java), [`Cache.java`](https://github.com/google/guava/blob/main/Cache.java), and [`CacheStats.java`](https://github.com/google/guava/blob/main/CacheStats.java) to understand the internal wiring of Guava's cache architecture.

## Frequently Asked Questions

### How do I implement a custom loading strategy for Guava caches?

Extend the abstract `CacheLoader` class and implement the `V load(K key)` method with your data retrieval logic. Pass this loader to `CacheBuilder.build()` to create a `LoadingCache` that automatically invokes your loading code on cache misses. For batch loading, override `loadAll()` to optimize bulk retrievals from your data source.

### What is the difference between maximumSize and maximumWeight in CacheBuilder?

`maximumSize(long size)` evicts entries when the cache contains more than the specified number of entries. `maximumWeight(long weight)` requires a custom `Weigher` implementation that assigns an integer weight to each entry, allowing eviction based on total resource consumption (such as bytes in memory) rather than entry count.

### How can I asynchronously refresh cached values without blocking readers?

Override `ListenableFuture<V> reload(K key, V oldValue)` in your `CacheLoader` and configure the cache with `refreshAfterWrite(duration, unit)`. This invokes your reload implementation asynchronously when an entry is requested after the refresh interval has passed, returning the old value immediately while the new value loads in the background.

### How do I monitor the performance of a custom Guava cache?

Enable statistics collection by calling `recordStats()` on your `CacheBuilder`. After building the cache, call `cache.stats()` to retrieve a `CacheStats` object containing hit count, miss count, eviction count, and load latency metrics.