Explain Codes LogoExplain Codes Logo

Java time-based map/cache with expiring keys

java
cache-management
time-based-cache
java-8
Alex KataevbyAlex Kataev·Oct 24, 2024
TLDR

Implement a self-expiring map in Java using Guava's Cache that evicts entries after a predetermined time. This is achieved using CacheBuilder for automatic key expiration:

import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; Cache<String, String> expiringCache = CacheBuilder.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .build(); expiringCache.put("key", "value"); // Expires after 10min, much like the time limit on my parking space!

This neat piece of code sets up a map where entries expire 10 minutes post-insertion. Make sure to include Guava in your project build to access CacheBuilder.

Exploring alternative solutions

If Guava doesn't strike a chord with you, fret not. There are other solutions to implement a time-based cache plus a few trade-offs to consider.

CompletableFuture for cleanup

CompletableFuture can help schedule cleanup tasks. It isn't a full cache solution, but can certainly find a place in simple contexts:

Map<String, Pair<String, CompletableFuture<Void>>> map = new ConcurrentHashMap<>(); map.put("key", Pair.of("value", CompletableFuture.runAsync(() -> { try { Thread.sleep(TimeUnit.MINUTES.toMillis(10)); map.remove("key"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }))); // This key will leave faster than the last slice of pizza at a party

This approach schedules a CompletableFuture to remove the key from the map after a delay. It's resource-light but doesn’t bode well with scale.

Scheduled thread pool for map cleaning

For greater control, employ a ScheduledExecutorService to periodically purge expired entries:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate(() -> map.keySet().removeIf(key -> /* expiration logic */), 0, 10, TimeUnit.MINUTES); // It's time for keys to face the expiration music!

This approach ensures that a cleanup runs predictably to clear expired items, especially useful when expiration logic isn't the simplest.

Other caching options

Apart from Guava, there's an entire repertoire of cache implementations to fit your use case. Let's take a quick look:

WeakConcurrentHashMap on GitHub

Perfect for memory-conscious work, it cleans up after entries are no longer in use:

WeakConcurrentHashMap<String, MyObject> map = new WeakConcurrentHashMap<>(timeout, unit); map.put("key", new MyObject()); // Loves to trash itself, a real self-cleaning oven

PassiveExpiringMap from Apache Commons

It's a simple, lightweight solution, but beware - it doesn't play well in concurrent scenarios without additional synchronization:

Map<String, String> expiringMap = new PassiveExpiringMap<>(TimeUnit.MINUTES.toMillis(10)); expiringMap.put("key", "value"); // It's just temporary, like my gym membership

Ehcache

A power-packed solution with advanced features for enterprise setups. Plus, it supports programmatic configuration:

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(); cacheManager.init(); Cache<Long, String> myCache = cacheManager.createCache("myCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(100)).withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(10)))); // This cache has more features than my smartphone!

Evaluate factors such as performance, concurrency, memory consumption, and maintenance complexity when choosing your caching solution.