Cache

A coroutine-based in-memory cache with composable behaviours. Thread-safe, multiplatform, and designed for hot paths.

Why a custom cache?

Most JVM caches (Caffeine, Guava) are excellent but JVM-only. When you need the same caching API in commonMain for code shared between JVM and JS, your options narrow quickly. Cache fills that gap with a clean builder DSL and composable behaviours that work on all Kotlin platforms.

We needed caching in Slumber's serialization hot path. It had to be fast, multiplatform, and not pull in a JVM-only dependency. So here we are.

The core idea: composable behaviours, async eviction

Instead of one cache with many configuration flags, Cache uses composable behaviours. Stack them to get exactly the policy you need. Eviction runs asynchronously in a coroutine background loop — reading and writing the cache is fast because the calling thread never pays for eviction work.

val cache = fastCache<String, UserProfile> {
    expireAfterWrite(10.minutes)         // evict 10 min after last write
    maxEntries(1000)                      // LRU after 1000 entries
    onEviction { key, _ ->                // react to evictions
        logger.debug("evicted $key")
    }
}

cache.put("user:42", fetchUser("42"))

val user = cache.getOrPut("user:99") {
    fetchUser("99")  // only called on miss
}

Behaviour matrix

Seven behaviours, four eviction strategies, two observers, one async refresh:

Behaviour Builder DSL Trigger TTL resets on
expireAfterAccess expireAfterAccess(5.minutes) No read or write within TTL Read + Write
expireAfterWrite expireAfterWrite(5.minutes) No write within TTL Write only
maxEntries maxEntries(1000) Entry count exceeds limit n/a (LRU by access)
maxMemoryUsage maxMemoryUsage(50_000_000) Estimated bytes exceed budget n/a (LRU by access)
refreshAfterWrite refreshAfterWrite(ttl) { ... } Soft: async reload; Hard: evicts Write only
onEviction onEviction { k, v -> ... } Observer — no eviction n/a
statistics statistics() Observer — no eviction n/a

What you get

Four Eviction Strategies

TTL by access, TTL by write, max entry count (LRU), and max memory with object size estimation.

Background Refresh

Stale-while-revalidate: serve old values while refreshing in the background. No reader ever waits.

Observability

Eviction callbacks and statistics (hit rate, miss count, eviction count) for tuning and debugging.

Thread-Safe

All operations are safe for concurrent access from multiple coroutines.

Kotlin Multiplatform

Works in commonMain, jvmMain, and jsMain.

Coroutine-Based

Eviction loop integrates with structured concurrency. Cancel the scope, the evictor stops cleanly.