eviction-insertion-order
First-in-first-out and last-in-first-out cache eviction
FirstInFirstOut and LastInFirstOut are EvictionStrategys that, when an Insert exceeds the capacity, remove the oldest or the newest earlier insertion, however often it was read.
Rationale
Why. Some data ages by when it arrived, however often it is read.
Key Concepts
Insertion order
Role. A key's place is set by its first Insert. A Hit, a Miss or a repeated Insert leaves it there.
Pattern. STRATEGY, OBSERVER
How It Works
Guarantees. No public parameter takes null and no public method returns it, declared with contrax's @PreCondition(IsNotNull.class) and @PostCondition(IsNotNull.class).
Thread safety. An instance is not thread-safe: events must reach update one at a time.
Lane
Purpose. Eviction where arrival order decides and reads must not change it.
Alternative. eviction-recency or eviction-frequency when a read should protect a key.
When to avoid. Workloads with popular keys: a key read constantly is still evicted in its turn.
Usage
FirstInFirstOut, room for two: a leaves first despite its reads, then b
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import software.visionary.cache.Cache;
import software.visionary.cache.capacity.fixed.FixedSize;
import software.visionary.cache.events.CacheEvent;
import software.visionary.cache.events.Evict;
import software.visionary.cache.eviction.insertion.order.FirstInFirstOut;
import software.visionary.cache.eviction.insertion.order.LastInFirstOut;
import software.visionary.cache.policy.CachePolicy;
import software.visionary.cache.topology.read.through.ReadThroughCacheFactory;
import software.visionary.events.Observer;
Map<String, Integer> source = Map.of("a", 1, "b", 2, "c", 3, "d", 4);
Supplier<Cache<String, Integer>> oldestLeaves = new ReadThroughCacheFactory<>(
key -> Optional.ofNullable(source.get(key)),
new CachePolicy<>(() -> new FixedSize<>(2), FirstInFirstOut::new, Instant::now));
Observer<CacheEvent<String, ?>> printEvictions = event -> {
if (event instanceof Evict<String, ?> evicted) { System.out.println("evicted " + evicted.key()); }
};
var cache = oldestLeaves.get();
cache.add(printEvictions);
Stream.of("a", "b", "a", "a", "c", "d").forEach(cache::apply);
Output
evicted a
evicted bLastInFirstOut, same reads: a stays, and each new key replaces the one before it
Supplier<Cache<String, Integer>> newestLeaves = new ReadThroughCacheFactory<>(
key -> Optional.ofNullable(source.get(key)),
new CachePolicy<>(() -> new FixedSize<>(2), LastInFirstOut::new, Instant::now));
var other = newestLeaves.get();
other.add(printEvictions);
Stream.of("a", "b", "a", "a", "c", "d").forEach(other::apply);
Output
evicted b
evicted cDependencies
software.visionary:cache:2026.10.13at Compilesoftware.visionary.contrax:processor:2026.10.13at Processorsoftware.visionary:junit-closure:2026.09.17at Testing
Views
Drawn from the release. A link in a view opens its source at the release tag.