Skip to content
Exposed docs1.11

Choosing a Cache Backend

Latest stable Based on Exposed release 1.11.0

The first decision is not the Redis client. Decide who must observe the same cached value, how long stale data is acceptable, and when a successful write must already be durable in the database.

Cache backend and write-policy decision map

NeedStart withWhyMain cost
One application processCaffeineNo network or external serviceEvery JVM owns different state
Shared state across instancesLettuce or Redisson remote cacheRedis is the common state ownerNetwork and Redis become part of availability
Hot local reads plus shared RedisNear CacheL1 avoids some round tripsTwo tiers must invalidate and expire coherently

Choose JDBC or R2DBC independently from the cache backend. JDBC adapters provide blocking paths, with suspended JDBC variants where available. R2DBC adapters keep repository operations suspend; that changes cancellation and transaction ownership, not the cache consistency model.

PatternRead missWrite ownerReturn meansUse when
Cache-asideApplication loads DB and cachesApplication transactionDB commit completed; invalidation may still failExisting service owns writes
Read-throughLoader reads DBCache loaderMiss load completedRepository should centralize miss handling
Write-throughConfigured writer updates DBCache map writerDB write completed or failedSynchronous durability is required
Write-behindLoader still handles readsBounded async writerCache accepted the write; DB may not be committedDelayed durability and loss window are acceptable
InvalidationNo loadCache only by defaultSelected cache state was removedDB remains the authority

An ordinary put is not proof of write-through. Check CacheWriteMode, LettuceCacheConfig/RedissonCacheConfig, and whether the concrete map has a database writer.

ConcernCaffeineLettuceRedisson
LocationIn-processRedis; selected paths add Caffeine near cacheRedis RMap/RLocalCachedMap
Key/value contractPrefix plus local ID/valueExplicit RedisCodec<String, E>; TTL applied on setConfigured Redisson codec; unsafe binary families require trust opt-in
Client lifecycleRepository owns local cache/scopeApplication owns RedisClient; repository closes connectionApplication owns RedissonClient
Partial failureQueue saturation or process lossRedis timeout/retry, dead letter, DB failureRedis future, writer/DB failure, optional DB deletion on invalidation
IsolationUnique prefix per testIsolated Redis and prefixIsolated Redis and map name

Lettuce is a good fit when the application wants explicit codecs and connection control. Redisson is a good fit when its loaded-map and local-cached-map policy is useful. That is an ownership decision, not a universal performance ranking.

Set TTL from the business staleness budget. In a local cache, another instance cannot see invalidation. In a near cache, both L1 and Redis must be cleared or synchronized. For Redisson, deleteFromDBOnInvalidate=false is the safe default: turning it on changes a cache operation into database deletion.

Key prefixes, cache names, codecs, and entity schemas are stored-data contracts. Roll out a migration or versioned namespace when any of them changes. Do not silently reinterpret old Redis bytes with a new codec.

  • Decide whether a cache backend outage fails the request, bypasses cache, or allows stale local data.
  • Bound retries, timeouts, write-behind queues, and shutdown drain time.
  • Observe queue depth, rejected writes, dead letters, circuit state, and invalidation lag.
  • Close repositories before application-owned Redis clients.
  • Test cancellation around R2DBC and suspended JDBC paths.
  • Use unique namespaces and clear them in test teardown.

Begin with Exposed cache foundation. Implement one backend in READ_ONLY/read-through mode, prove cache-only invalidation, then add the write policy. Near cache and write-behind should be the last steps because they add a second consistency boundary or a delayed durability window.