### Install GraalVM using sdkman.io Source: https://github.com/ben-manes/caffeine/blob/master/examples/graal-native/README.md Installs and sets the GraalVM distribution for use. Ensure sdkman.io is installed and configured. ```bash sdk install java 22-graal ``` ```bash sdk use java 22-graal ``` ```bash export JAVA_HOME=${SDKMAN_DIR}/candidates/java/current ``` -------------------------------- ### Simulator Configuration via System Properties Source: https://github.com/ben-manes/caffeine/wiki/Simulator Provides an example of supplying additional configuration to the simulator using system properties, such as specifying trace file paths. ```bash -Dcaffeine.simulator.files.paths.0=multi3.trace.gz ``` -------------------------------- ### Implement CacheWriter for LoadingCache Source: https://github.com/ben-manes/caffeine/wiki/Writer Example of building a LoadingCache with a custom CacheWriter. The writer handles the synchronization with an external resource during cache writes. This setup is not compatible with weak keys or AsyncLoadingCache. ```java LoadingCache graphs = Caffeine.newBuilder() .writer(new CacheWriter() { @Override public void write(Key key, Graph graph) { // write to storage or secondary cache } @Override public void delete(Key key, Graph graph, RemovalCause cause) { // delete from storage or secondary cache } }) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Asynchronously Get Cache Entries Source: https://github.com/ben-manes/caffeine/wiki/Population Shows how to retrieve entries from an AsyncLoadingCache. `get` retrieves a single entry, while `getAll` retrieves multiple entries, both returning `CompletableFuture` objects. ```java // Lookup and asynchronously compute an entry if absent CompletableFuture graph = cache.get(key); // Lookup and asynchronously compute entries that are absent CompletableFuture> graphs = cache.getAll(keys); ``` -------------------------------- ### Strong Interner Example Source: https://github.com/ben-manes/caffeine/wiki/Interner Demonstrates the basic usage of a strong interner to deduplicate string objects. When an equal string is interned, the same instance is returned, as verified by the identity check (==). ```java Interner interner = Interner.newStrongInterner(); String s1 = interner.intern(new String("value")); String s2 = interner.intern(new String("value")); assert s1 == s2 ``` -------------------------------- ### Create Guice Injector with Cache Annotations Source: https://github.com/ben-manes/caffeine/wiki/JCache Instantiate a Guice injector with the `CacheAnnotationsModule` to enable JSR-107 annotation support. This setup is necessary for using JCache annotations with Guice. ```java Injector injector = Guice.createInjector(new CacheAnnotationsModule()); ``` -------------------------------- ### Implement Timeout Policy for Cache Operations Source: https://github.com/ben-manes/caffeine/blob/master/examples/resilience-failsafe/README.md Apply a timeout policy to cancel cache operations that exceed a specified duration. This example combines timeout with a retry policy for comprehensive resilience. Ensure the timeout duration is appropriate for expected operation times. ```java var timeout = Timeout.builder(Duration.ofSeconds(10)).withInterrupt().build(); var failsafe = Failsafe.with(timeout, retryPolicy); Cache cache = Caffeine.newBuilder().build(); failsafe.get(() -> cache.get(key, key -> /* timeout */ )); ``` -------------------------------- ### Compile and run native binary Source: https://github.com/ben-manes/caffeine/blob/master/examples/graal-native/README.md Compiles the project into a native executable and then runs it. This step assumes the native image configuration is complete. ```bash ./gradlew nativeRun ``` -------------------------------- ### Simulator JVM Arguments Source: https://github.com/ben-manes/caffeine/wiki/Simulator Shows how to provide custom JVM arguments, like memory allocation, when running the simulator. ```bash -PjvmArgs=-Xmx6g ``` -------------------------------- ### Try the native binary directly Source: https://github.com/ben-manes/caffeine/blob/master/examples/graal-native/README.md Executes the compiled native binary directly from its build output directory. This is useful for quick testing after compilation. ```bash ./build/native/nativeCompile/graal-native ``` -------------------------------- ### Run on JVM with agent for metadata Source: https://github.com/ben-manes/caffeine/blob/master/examples/graal-native/README.md Executes the Gradle build with an agent to capture necessary class metadata for native image compilation. ```bash ./gradlew run -Pagent ``` -------------------------------- ### Assemble Project with Gradle Source: https://github.com/ben-manes/caffeine/wiki/Contribute Run the assemble task to build the project. This is useful for IDE synchronization or command-line builds. ```gradle gradlew assemble ``` -------------------------------- ### Build Guava LoadingCache with Caffeine Source: https://github.com/ben-manes/caffeine/wiki/Guava Demonstrates how to build a Guava LoadingCache using Caffeine's builder and a Guava CacheLoader. This is useful for migrating from Guava to Caffeine while maintaining a familiar interface. ```java LoadingCache graphs = CaffeinatedGuava.build( Caffeine.newBuilder().maximumSize(10_000), new CacheLoader() { // Guava's CacheLoader @Override public Graph load(Key key) throws Exception { return createExpensiveGraph(key); } }); ``` -------------------------------- ### Copy metadata for source configuration Source: https://github.com/ben-manes/caffeine/blob/master/examples/graal-native/README.md Copies the captured metadata to the appropriate directory for use as source configuration in the native image build process. ```bash ./gradlew metadataCopy --task run --dir src/main/resources/META-INF/native-image ``` -------------------------------- ### Run tests against native binary Source: https://github.com/ben-manes/caffeine/blob/master/examples/graal-native/README.md Executes the project's test suite against the compiled native binary to ensure correctness and compatibility. ```bash ./gradlew nativeTest ``` -------------------------------- ### Run Caffeine Simulator (Single Run) Source: https://github.com/ben-manes/caffeine/wiki/Simulator Executes the simulator for a single run, displaying an ASCII table by default. No additional arguments are required for basic usage. ```bash gradlew simulator:run -q ``` -------------------------------- ### Weak Interner with LoadingCache Source: https://github.com/ben-manes/caffeine/wiki/Interner Shows how to combine a weak interner with a Caffeine LoadingCache. This allows interned keys to be garbage collected if they are no longer strongly referenced, while the cache entry is associated with the canonical key instance. ```java LoadingCache graphs = Caffeine.newBuilder() .weakKeys() .build(key -> createExpensiveGraph(key)); Interner interner = Interner.newWeakInterner(); Key canonical = interner.intern(key); Graph graph = graphs.get(canonical); ``` -------------------------------- ### Synchronous Loading Cache in Java Source: https://github.com/ben-manes/caffeine/wiki/Population Shows how to use a LoadingCache for automatic cache population. The cache computes values using a provided CacheLoader when entries are absent. ```java LoadingCache cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(key -> createExpensiveGraph(key)); // Lookup and compute an entry if absent, or null if not computable Graph graph = cache.get(key); // Lookup and compute entries that are absent Map graphs = cache.getAll(keys); ``` -------------------------------- ### Build Async Cache with Synchronous or Asynchronous Computation Source: https://github.com/ben-manes/caffeine/wiki/Population Demonstrates building an AsyncLoadingCache. Use `buildAsync` with a `CacheLoader` for synchronous computations wrapped asynchronously, or with an `AsyncCacheLoader` for native asynchronous computations. ```java AsyncLoadingCache cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(10, TimeUnit.MINUTES) // Either: Build with a synchronous computation that is wrapped as asynchronous .buildAsync(key -> createExpensiveGraph(key)); // Or: Build with a asynchronous computation that returns a future .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor)); ``` -------------------------------- ### Manual Cache Operations in Java Source: https://github.com/ben-manes/caffeine/wiki/Population Demonstrates manual cache management using Caffeine's Cache interface. Use for explicit control over entry retrieval, insertion, and invalidation. ```java Cache cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10_000) .build(); // Lookup an entry, or null if not found Graph graph = cache.getIfPresent(key); // Lookup and compute an entry if absent, or null if not computable graph = cache.get(key, k -> createExpensiveGraph(key)); // Insert or update an entry cache.put(key, graph); // Remove an entry cache.invalidate(key); ``` -------------------------------- ### Implement CacheLoader load and loadAll methods Source: https://github.com/ben-manes/caffeine/blob/master/examples/coalescing-bulkloader-reactor/README.md Provides the standard CacheLoader interface methods for loading single keys or multiple keys. The loadAll method directly applies the mapping function, while the load method delegates to loadAll for a single key. ```java @Override public V load(K key) { return loadAll(Set.of(key)).get(key); } @Override public Map loadAll(Set keys) { return mappingFunction.apply(keys); } ``` -------------------------------- ### Execute Static Analysis Script Source: https://github.com/ben-manes/caffeine/wiki/Contribute Run the static analysis script located in the .github/scripts directory. This task is not enabled by default. ```shell .github/scripts/analyze.sh ``` -------------------------------- ### Initialize CoalescingBulkLoader with Reactor Sinks Source: https://github.com/ben-manes/caffeine/blob/master/examples/coalescing-bulkloader-reactor/README.md Sets up a bulk loader that buffers requests using Reactor's Sinks.many().unicast().onBackpressureBuffer(). It configures the buffer size, timeout, parallelism for bulk loads, and the mapping function. The sink processes buffered requests, applies parallelism, and subscribes to handle the batches. ```java public final class CoalescingBulkLoader implements CacheLoader { private final Function, Map> mappingFunction; private final Sinks.Many> sink; /** * @param maxSize the maximum entries to collect before performing a bulk request * @param maxTime the maximum duration to wait before performing a bulk request * @param parallelism the number of parallel bulk loads that can be performed * @param mappingFunction the function to compute the values */ public CoalescingBulkLoader(int maxSize, Duration maxTime, int parallelism, Function, Map> mappingFunction) { this.sink = Sinks.many().unicast().onBackpressureBuffer(); this.mappingFunction = requireNonNull(mappingFunction); sink.asFlux() .bufferTimeout(maxSize, maxTime) .map(requests -> requests.stream().collect( toMap(Entry::getKey, Entry::getValue))) .parallel(parallelism) .runOn(Schedulers.boundedElastic()) .subscribe(this::handle); } ``` -------------------------------- ### Run Caffeine Simulator (Multiple Runs) Source: https://github.com/ben-manes/caffeine/wiki/Simulator Performs multiple simulation runs, generates a combined report, and renders a chart. Supports configuration of maximum size, theme, and title. ```bash gradlew simulator:simulate -q \ --maximumSize= \ --theme= \ --title= ``` -------------------------------- ### Run JMH Benchmarks with Gradle Source: https://github.com/ben-manes/caffeine/wiki/Contribute Execute Java Microbenchmark Harness (JMH) benchmarks using Gradle. Specify a class name pattern to include specific benchmarks. ```gradle gradlew jmh -PincludePattern=[class-name pattern] ``` -------------------------------- ### IndexedCache Construction and Usage Source: https://github.com/ben-manes/caffeine/blob/master/examples/indexable/README.md Constructs an IndexedCache with primary and secondary keys, cache configuration, and a data loader. Demonstrates retrieving cached entries using different key types. ```java var cache = new IndexedCache.Builder() .primaryKey(user -> new UserById(user.id())) .addSecondaryKey(user -> new UserByEmail(user.email())) .addSecondaryKey(user -> new UserByLogin(user.username())) .expireAfterWrite(Duration.ofMinutes(5)) .maximumSize(10_000) .build(this::findUser); var userByEmail = cache.get(new UserByEmail("john.doe@example.com")); var userByLogin = cache.get(new UserByLogin("john.doe")); assertThat(userByEmail).isSameInstanceAs(userByLogin); ``` -------------------------------- ### Build a Loading Cache Source: https://github.com/ben-manes/caffeine/blob/master/README.md Construct a cache with maximum size, expiration, and refresh policies. The cache automatically loads entries using a provided function. ```java LoadingCache graphs = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(Duration.ofMinutes(5)) .refreshAfterWrite(Duration.ofMinutes(1)) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Configure Caffeine JCache Provider Source: https://github.com/ben-manes/caffeine/blob/master/examples/hibernate/README.md Define cache settings for Hibernate in Caffeine's `application.conf` using the reference format. ```hocon caffeine.jcache { default { monitoring.statistics = true } # Hibernate framework caches default-query-results-region {} default-update-timestamps-region {} # Hibernate application caches com.github.benmanes.caffeine.examples.hibernate.User {} } ``` -------------------------------- ### Run Object Layout Task with Gradle Source: https://github.com/ben-manes/caffeine/wiki/Contribute Inspect Java object layouts using a Gradle task. Provide the class name to analyze. ```gradle gradlew [object-layout task] --class=[class-name] ``` -------------------------------- ### Configure LoadingCache with refreshAfterWrite Source: https://github.com/ben-manes/caffeine/wiki/Refresh Use `refreshAfterWrite` to make entries eligible for asynchronous refresh after a specified duration. A refresh is only initiated when the entry is queried. ```java LoadingCache graphs = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(Duration.ofMinutes(5)) .refreshAfterWrite(Duration.ofMinutes(1)) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Configure Hibernate Second-Level Cache Source: https://github.com/ben-manes/caffeine/blob/master/examples/hibernate/README.md Specify the JCache provider and enable the second-level cache in `hibernate.properties`. ```properties hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=jcache hibernate.javax.cache.provider=com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider ``` -------------------------------- ### Asynchronous Cache Operations in Java Source: https://github.com/ben-manes/caffeine/wiki/Population Illustrates using an AsyncCache for non-blocking cache computations. Entries are computed asynchronously on an Executor and returned as CompletableFuture. ```java AsyncCache cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10_000) .buildAsync(); // Lookup an entry, or null if not found CompletableFuture graph = cache.getIfPresent(key); // Lookup and asynchronously compute an entry if absent graph = cache.get(key, k -> createExpensiveGraph(key)); // Insert or update an entry cache.put(key, graph); // Remove an entry cache.synchronous().invalidate(key); ``` -------------------------------- ### Evict when key or value are not strongly reachable Source: https://github.com/ben-manes/caffeine/wiki/Eviction Use `weakKeys()` and `weakValues()` to allow entries to be garbage-collected if no other strong references exist. This causes key and value comparisons to use identity equality (==) instead of `equals()`. ```java LoadingCache graphs = Caffeine.newBuilder() .weakKeys() .weakValues() .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Parameterized Cache Testing with @CacheSpec Source: https://github.com/ben-manes/caffeine/wiki/Contribute Demonstrates parameterized testing for cache configurations using @CacheSpec. The test method accepts Cache and CacheContext for inspecting execution configuration. ```java public final class CacheTest { @ParameterizedTest @CacheSpec(keys = { ReferenceType.STRONG, ReferenceType.WEAK }, values = { ReferenceType.STRONG, ReferenceType.WEAK, ReferenceType.SOFT }, maximumSize = { MaximumSize.DISABLED, MaximumSize.FULL, MaximumSize.UNREACHABLE }) public void getIfPresent_notFound( Cache cache, CacheContext context) { // This test is run against at least 72 different cache configurations // (2 key types) * (3 value types) * (3 max sizes) * (4 population modes) assertThat(cache.getIfPresent(context.getAbsentKey()), is(nullValue()); assertThat(cache.stats(), both(hasMissCount(1)).and(hasHitCount(0))); } } ``` -------------------------------- ### Test Cache Eviction with FakeTicker Source: https://github.com/ben-manes/caffeine/wiki/Testing Use FakeTicker to simulate time passing and test cache eviction without waiting for real-time. Ensure to use Cache.cleanUp() to trigger eviction immediately if tests depend on it. ```java FakeTicker ticker = new FakeTicker(); // Guava's testlib Cache cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .executor(Runnable::run) .ticker(ticker::read) .maximumSize(10) .build(); cache.put(key, graph); ticker.advance(30, TimeUnit.MINUTES) assertThat(cache.getIfPresent(key), is(nullValue())); ``` -------------------------------- ### Configure Removal and Eviction Listeners Source: https://github.com/ben-manes/caffeine/wiki/Removal Specify listeners to perform operations when cache entries are removed. Removal listeners execute asynchronously, while eviction listeners execute synchronously only on eviction. ```java Cache graphs = Caffeine.newBuilder() .evictionListener((Key key, Graph graph, RemovalCause cause) -> System.out.printf("Key %s was evicted (%s)%n", key, cause)) .removalListener((Key key, Graph graph, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(); ``` -------------------------------- ### Evict After Access Source: https://github.com/ben-manes/caffeine/wiki/Eviction Use `expireAfterAccess` to remove entries after a specified duration of inactivity. This is ideal for session-bound data that should expire due to lack of use. ```java // Evict based on a fixed expiration policy LoadingCache graphs = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Rewrite Trace Formats Source: https://github.com/ben-manes/caffeine/wiki/Simulator Converts traces from one format to another using the rewriter utility. Specify input and output formats, input files, and the output file path. ```bash gradlew :simulator:rewrite -q \ --inputFormat= \ --inputFiles= \ --outputFormat= \ --outputFile= ``` -------------------------------- ### Implement Async Coalescing Bulk Loader Source: https://github.com/ben-manes/caffeine/blob/master/examples/coalescing-bulkloader-reactor/README.md This Java class implements the AsyncCacheLoader interface to handle bulk asynchronous loading requests. It uses Reactor's Sinks and Flux to buffer and process requests in parallel. Ensure Reactor is available for this implementation. ```java public final class CoalescingBulkLoader implements AsyncCacheLoader { private final Function, Map> mappingFunction; private final Sinks.Many> sink; public CoalescingBulkLoader(int maxSize, Duration maxTime, int parallelism, Function, Map> mappingFunction) { this.sink = Sinks.many().unicast().onBackpressureBuffer(); this.mappingFunction = requireNonNull(mappingFunction); sink.asFlux() .bufferTimeout(maxSize, maxTime) .map(requests -> requests.stream().collect( toMap(Entry::getKey, Entry::getValue))) .parallel(parallelism) .runOn(Schedulers.boundedElastic()) .subscribe(this::handle); } @Override public synchronized CompletableFuture asyncLoad(K key, Executor e) { var entry = Map.entry(key, new CompletableFuture()); sink.tryEmitNext(entry).orThrow(); return entry.getValue(); } private void handle(Map> requests) { try { var results = mappingFunction.apply(requests.keySet()); requests.forEach((key, result) -> result.complete(results.get(key))); } catch (Throwable t) { requests.forEach((key, result) -> result.completeExceptionally(t)); } } } ``` -------------------------------- ### SQL Schema for User Information Source: https://github.com/ben-manes/caffeine/blob/master/examples/indexable/README.md Defines the 'user_info' table with a primary key and unique indexes for email and username, suitable for an indexed cache scenario. ```sql CREATE TABLE user_info ( id bigserial primary key, first_name varchar(255) NOT NULL, last_name varchar(255) NOT NULL, email varchar(255) NOT NULL, username varchar(255) NOT NULL, password_hash varchar(255) NOT NULL, created_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP, modified_on timestamp with time zone DEFAULT CURRENT_TIMESTAMP ); CREATE UNIQUE INDEX user_info_email_idx ON user_info (email); CREATE UNIQUE INDEX user_info_username_idx ON user_info (username); ``` -------------------------------- ### Evict by Maximum Size Source: https://github.com/ben-manes/caffeine/wiki/Eviction Use `maximumSize` to evict entries when the cache exceeds a specified number of entries. This policy is suitable when cache entries have a uniform size. ```java // Evict based on the number of entries in the cache LoadingCache graphs = Caffeine.newBuilder() .maximumSize(10_000) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Process batched requests and complete futures Source: https://github.com/ben-manes/caffeine/blob/master/examples/coalescing-bulkloader-reactor/README.md Handles a batch of requests by applying the mapping function to retrieve values for all keys in the batch. It then completes the corresponding CompletableFuture for each key with its retrieved value or completes them exceptionally if an error occurs during the mapping function execution. ```java private void handle(Map> requests) { try { var results = mappingFunction.apply(requests.keySet()); requests.forEach((key, result) -> result.complete(results.get(key))); } catch (Throwable t) { requests.forEach((key, result) -> result.completeExceptionally(t)); } } ``` -------------------------------- ### Configure Cache Expiration with System Scheduler Source: https://github.com/ben-manes/caffeine/wiki/Cleanup Configures a LoadingCache with a 10-minute expiration after write and uses the system scheduler for maintenance. This is suitable for high-throughput caches where automatic cleanup is sufficient. ```java LoadingCache graphs = Caffeine.newBuilder() .scheduler(Scheduler.systemScheduler()) .expireAfterWrite(10, TimeUnit.MINUTES) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Manual Cache Cleanup with Java 9+ Cleaner Source: https://github.com/ben-manes/caffeine/wiki/Cleanup Demonstrates using Java 9+'s `Cleaner` to trigger cache maintenance for reference-based entries (weak/soft values/keys). The `Cleaner` registers a runnable that invokes `Cache.cleanUp()` when the registered object is garbage collected. ```java Cache graphs = Caffeine.newBuilder().weakValues().build(); Cleaner cleaner = Cleaner.create(); cleaner.register(graph, graphs::cleanUp); graphs.put(key, graph); ``` -------------------------------- ### Enable Statistics Collection in Caffeine Cache Source: https://github.com/ben-manes/caffeine/wiki/Statistics Use `Caffeine.recordStats()` when building a cache to enable statistics collection. This allows you to monitor cache performance metrics. ```java Cache graphs = Caffeine.newBuilder() .maximumSize(10_000) .recordStats() .build(); ``` -------------------------------- ### Verify Hibernate Cache Behavior Source: https://github.com/ben-manes/caffeine/blob/master/examples/hibernate/README.md Access an entity to observe cache misses and hits, and check Hibernate statistics. ```java // miss on first access sessionFactory.fromSession(session -> session.get(User.class, id)); assertThat(sessionFactory.getStatistics().getSecondLevelCacheMissCount()).isEqualTo(1); // hit on lookup sessionFactory.fromSession(session -> session.get(User.class, id)); assertThat(sessionFactory.getStatistics().getSecondLevelCacheHitCount()).isEqualTo(1); ``` -------------------------------- ### Evict After Variable Duration Source: https://github.com/ben-manes/caffeine/wiki/Eviction Use `expireAfter` with a custom `Expiry` implementation to define variable expiration durations based on entry attributes or external factors. ```java // Evict based on a varying expiration policy LoadingCache graphs = Caffeine.newBuilder() .expireAfter(Expiry.creating((Key key, Graph graph) -> Duration.between(Instant.now(), graph.creationDate().plusHours(5)))) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Pinning an Entry with Zero Weight Source: https://github.com/ben-manes/caffeine/wiki/Faq Exclude an entry from maximum size eviction by assigning it a weight of zero. This requires a custom Weigher to evaluate if the entry is pinned. ```java cache.asMap().compute( key, (k, v) -> (v == null || !isPinned(v)) ? Cache.வதாக(k, v, Weight.ZERO) : v); ``` -------------------------------- ### Evict After Write Source: https://github.com/ben-manes/caffeine/wiki/Eviction Use `expireAfterWrite` to remove entries after a specified duration from their creation or last replacement. This is suitable for data that becomes stale over time. ```java LoadingCache graphs = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .build(key -> createExpensiveGraph(key)); ``` -------------------------------- ### Intercept Eviction and Compute Cache Entries Source: https://github.com/ben-manes/caffeine/wiki/Compute Use an eviction listener to atomically intercept entry evictions and `compute` methods for complex cache update logic. The cache may block subsequent operations for an entry until the computation completes. ```java Cache graphs = Caffeine.newBuilder() .evictionListener((Key key, Graph graph, RemovalCause cause) -> { // atomically intercept the entry's eviction }).build(); graphs.asMap().compute(key, (k, v) -> { Graph graph = createExpensiveGraph(key); ... // update a secondary store return graph; }); ```