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 b

LastInFirstOut, 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 c

Dependencies

  • software.visionary:cache:2026.10.13 at Compile
  • software.visionary.contrax:processor:2026.10.13 at Processor
  • software.visionary:junit-closure:2026.09.17 at Testing

Views

Drawn from the release. A link in a view opens its source at the release tag.

architecture.svg
architecture.svg
classes.software.visionary.cache.eviction.insertion.order.svg
classes.software.visionary.cache.eviction.insertion.order.svg
packages.svg
packages.svg