Cache eviction removes an entry selected by the cache name and key so a later intercepted read can recompute its value.
Spring cache eviction: invalidate the same tenant key used by the read
The downloadable Spring source kit pins Java 21 and Spring Boot 4.0.8 with its managed dependencies. Run mvn test to check the named fixture.
Use one key definition
ReceiptCacheEdits uses a Key record containing tenant and id for both Cacheable and CacheEvict. The test reads tenant-a receipt seven and tenant-b receipt seven, changes only tenant-a, then checks that the next tenant-a read sees the new value while tenant-b retains its own value. Using id alone would collapse unrelated records into the same cache entry.
The service comes from an annotation-enabled Spring context. A direct object or self-invocation can bypass cache interception, just as other proxy-based concerns can be bypassed. Cache key rules and Proxy boundaries explain those separate failure paths.
Eviction timing is not transaction durability
The fixture stores values in a ConcurrentHashMap and uses a local ConcurrentMapCacheManager. By default the method completes before its matching entry is evicted. A failed method and an explicit beforeInvocation policy have different consequences; choose that policy deliberately rather than treating all invalidation annotations as identical.
This teaching store has no database transaction, expiry, capacity or cross-process replication. A database write that rolls back after cache changes can require transaction-aware cache design; another application instance has its own local state. The test cannot establish consistency across servers from one process-local map.
Checked source
package in.aitrove.contracts;
import java.util.concurrent.*;
import org.springframework.cache.annotation.*;
public class ReceiptCacheEdits {
public record Key(String tenant,long id) {}
private final ConcurrentMap<Key,String> data=new ConcurrentHashMap<>();
@Cacheable(cacheNames="contractReceipts",key="#key")
public String read(Key key){return data.getOrDefault(key,"missing");}
@CacheEvict(cacheNames="contractReceipts",key="#key")
public void replace(Key key,String value){data.put(key,value);}
}Test the boundary
CacheEvictionContractTest.proxiedEditEvictsOnlyTheMatchingTenantKey checks this contract in the source kit. Excerpts belong to the named classes; use the downloadable files for imports, configuration and assertions.
Costs and boundaries
Local map access has expected constant-time lookup, with retained storage proportional to distinct keys. Neither the backing store nor the cache has a size limit or expiry in this fixture. Production use needs retention and invalidation policies plus an explicit consistency contract for concurrent reads and writes.
Common Mistakes
- Match read and eviction keys, including tenant identity.
- Call through the configured cache proxy.
- Do not claim distributed consistency or bounded memory from a local map test.
Read next
Spring cache keys: separate tenants and test the loader count, Spring transaction events: run a listener after commit without claiming durability, Spring AOP proxies: self-invocation bypasses proxy advice, Java LinkedHashMap LRU cache: access order changes eviction.
Related boundary
Spring Redis cache writer: compound operations and replica stampedes
