# cache

`software.visionary:cache:2026.10.13` · https://codeberg.org/caching/api

Cache: a Function that keeps what it answered, managed by an eviction STRATEGY

Contracts for a cache: a <code>Cache&lt;KEY, VALUE&gt;</code> is a <code>Function</code> from key to <code>Optional&lt;VALUE&gt;</code> with its own <code>Capacity</code> and <code>EvictionStrategy</code>, publishing <code>CacheEvent</code>s to its OBSERVERs. This module holds the contracts, not a cache.

## Rationale

**Why.** Code that reads, bounds or evicts from a cache should not depend on which cache it is given.

## Key Concepts

### Cache

**Role.** <code>apply(key)</code> answers the value when present, otherwise empty. <code>capacity()</code> and <code>strategy()</code> return the STRATEGYs that bound and evict this cache. <code>MutableCache</code> adds <code>accept(key, value)</code>.

**Pattern.** STRATEGY

### Lifecycle events

**Role.** Sealed <code>CacheEvent</code> permits <code>Hit</code>, <code>Miss</code>, <code>Insert</code> and <code>Evict</code>, so a <code>switch</code> over them is exhaustive. A <code>ValueInsertion</code> is an <code>Insert</code> that carries the value, and a cache may publish its own.

**Pattern.** OBSERVER

### Capacity and eviction

**Role.** Each observes a cache's events. <code>Capacity.exceeded()</code> says whether its bound requires eviction, independently of what is chosen for removal. <code>EvictionStrategy.evict(key)</code> removes an entry; an absent key has no effect.

**Pattern.** STRATEGY, OBSERVER

## How It Works

**Guarantees.** No public parameter takes null and no public method returns it, declared with contrax's <code>@PreCondition(IsNotNull.class)</code> and <code>@PostCondition(IsNotNull.class)</code>.

## Lane

**Purpose.** The types a cache, its capacity and its eviction are written against.

**Alternative.** <code>java.util.Map</code> when nothing is ever forgotten.

**When to avoid.** A value that must not be lost: a cache may forget anything it keeps.

## Usage

React to a lifecycle event

```java
import java.time.Instant;
import software.visionary.cache.events.CacheEvent;
import software.visionary.cache.events.Evict;
import software.visionary.cache.events.Hit;
import software.visionary.cache.events.Insert;
import software.visionary.cache.events.Miss;
import software.visionary.cache.events.ValueInsertion;

String described(CacheEvent<String, ?> event) {
    return switch (event) {
        case Hit<String, ?> hit -> "hit " + hit.key();
        case Miss<String, ?> miss -> "miss " + miss.key();
        case ValueInsertion<String, ?> kept -> "insert " + kept.key() + "=" + kept.value();
        case Insert<String, ?> keyOnly -> "insert " + keyOnly.key();
        case Evict<String, ?> evict -> "evict " + evict.key();
    };
}

Instant now = Instant.now();
System.out.println(described(new Miss.Occurred<>("answer", now)));
System.out.println(described(new Insert.Value<>("answer", 42, now)));
System.out.println(described(new Evict.Occurred<>("answer", now)));

```

Output

```text
miss answer
insert answer=42
evict answer

```

A capacity of your own

```java
final class TotalKeyLength implements Capacity<String> {
    private final int limit;
    private final Set<String> kept = new HashSet<>();

    TotalKeyLength(int limit) {
        this.limit = limit;
    }

    @Override public boolean exceeded() {
        return kept.stream().mapToInt(String::length).sum() > limit;
    }

    @Override public void update(CacheEvent<String, ?> event) {
        if (event instanceof Insert<String, ?>) {
            kept.add(event.key());
        } else if (event instanceof Evict<String, ?>) {
            kept.remove(event.key());
        }
    }
}

Instant now = Instant.now();
Capacity<String> capacity = new TotalKeyLength(9);
capacity.update(new Insert.Key<>("four", now));
capacity.update(new Insert.Key<>("five", now));
System.out.println(capacity.exceeded());
capacity.update(new Insert.Key<>("three", now));
System.out.println(capacity.exceeded());
capacity.update(new Evict.Occurred<>("four", now));
System.out.println(capacity.exceeded());

```

Output

```text
false
true
false
```

## Dependencies

- `software.visionary:events:2026.10.13` at Compile
- `software.visionary.contrax:processor:2026.10.13` at Processor

