# Audit Map Contract

> Audit ConcurrentMap and Map contract compliance for asMap() view

- Skill: `ben-manes/audit-map-contract` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ben-manes/audit-map-contract`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ben-manes/audit-map-contract/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: ben-manes (https://skillmd.com/u/ben-manes)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/ben-manes/audit-map-contract

---


Audit compliance with `java.util.concurrent.ConcurrentMap` and `java.util.Map` contracts,
and Caffeine's alignment with the Java Collections Framework.

## Cross-reference the latest OpenJDK source — do NOT reason from JavaDoc memory

The JavaDoc is silent or ambiguous on the behaviors that actually bite (null inside a bulk
collection arg, `containsAll(self)`, `equals` across map types, the *optional* NPE points,
default-method bodies). **WebFetch the real source and read the method body** before
asserting a contract. Track `master` (latest) — we stay pragmatically current, not pinned
to a JDK version. Fetch the raw form (`raw.githubusercontent.com/openjdk/jdk/master/src/
java.base/share/classes/…`), not the `blob` page. These files are large (CHM ~6500 lines)
and WebFetch answers a *prompt* over the content with a small model, so a generic "dump the
file" truncates — **prompt for the specific method** ("quote the exact body of
`CollectionView.containsAll`"), one method per fetch:

- **ConcurrentHashMap** — the primary reference; `asMap()` is a `ConcurrentMap` and closest
  to CHM:
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java`
- **ConcurrentSkipListMap** — the *other* JDK `ConcurrentMap`; the tie-breaker on whether a
  divergence from CHM is legal (e.g. it also throws UOE on `entrySet().add`, where CHM's
  put-through is a nonstandard v8-rewrite addition):
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java`
- **ConcurrentMap** — interface + default methods (`getOrDefault`, `compute*`, `merge`,
  `replaceAll`):
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java`
- **Map** — base contract + interface default methods:
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/Map.java`
- **AbstractMap** — the default-method bodies the views inherit (`AbstractMap.equals`,
  and via `AbstractCollection`/`AbstractSet` the `containsAll`/`removeAll`/`retainAll` loops):
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/AbstractMap.java`
- **HashMap** — the most common `equals`/`hashCode` comparison target:
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/HashMap.java`
- **IdentityHashMap** — identity-semantics edge, relevant to weak-key identity and the
  async future-keyed raw view:
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/IdentityHashMap.java`
- **WeakHashMap** — relevant to weak keys with automatic removal:
  `https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/classes/java/util/WeakHashMap.java`
- **Guava `LocalCache`** — the reference for Caffeine's Guava-compat behavior (the
  `CaffeinatedGuava` facade + the uniform null-leniency the philosophy below cites; its
  `asMap()` views, removal causes, loader/exception translation):
  `https://raw.githubusercontent.com/google/guava/master/guava/src/com/google/common/cache/LocalCache.java`

## Alignment philosophy — close to CHM, diverge when better

**Be pragmatically close to CHM, but Caffeine may diverge where its behavior is better** —
and when it does, the divergence must be *coherent* and recorded (design-decisions.md /
the cross-model ledger). Do not treat "differs from CHM" as a bug by itself:

- **CHM is internally inconsistent.** e.g. `removeAll([null])` throws NPE while
  `containsAll([null])` returns false — an artifact of which methods happened to get an
  explicit null-guard, not a contract. **Don't chase CHM's inconsistencies.**
- **Caffeine's own coherent principles are the target**, not any one JDK map's quirks:
  null-hostile on *direct* single-element ops (`put`/`get`/`containsKey`/`contains(null)` →
  NPE — nulls are rejected), but null-tolerant of a null *element* in a *bulk* collection
  arg (`containsAll`/`removeAll` skip it — a null is trivially absent); weakly-consistent
  views; `size()` is an estimate; expired/collected entries filtered from queries and
  iteration. The `CaffeinatedGuava` facade is the deliberate exception — uniformly
  null-lenient (Guava-compat), overriding native null-hostility only where Guava diverges.
- A contract-**optional** behavior (permitted NPE, ordering, `equals` across types) has a
  legal **spread** — check several impls (CHM / CSLM / HashMap / Guava), not one, and note
  that the collections testlibs usually **tolerate both** legal choices (e.g. guava-testlib
  `testContainsAll_nullNotAllowed` does `assertFalse(...)` with `catch (NPE tolerated)`).
  Flag a divergence only when Caffeine is the *incoherent* one, or diverges from *both* CHM
  and Guava with no better rationale.

## Settle divergences empirically, not from memory

For any "does Caffeine match CHM/Guava?" question, compile a tiny harness and run Caffeine
**side-by-side with real CHM / CSLM / Guava** — don't guess (this session, "we mirror CHM"
on `containsAll([null])` was empirically wrong — both CHM *and* Guava return false, Caffeine
NPE'd; and the CHMv8 `entrySet().add` history was non-obvious):

- Classpath: the **built jar** `caffeine/build/libs/caffeine-*.jar` (it has the generated
  node classes like `SSMS`; `build/classes/java/main` does NOT → factory reflection throws),
  plus real guava from `~/.gradle/caches/**/guava-*.jar`.
- Run under **JDK 26** (`~/.gradle/jdks/*26*/**/bin/java`) — the classes are that bytecode level.
- Gotcha: a `timeout … | grep` pipeline reports *grep's* exit code, not gradle's — read the
  `BUILD` line.
- For a native-view fix, `AsMapTest`'s `map` param is polymorphic: `compute=ASYNC` yields the
  async **sync-view** (`LocalAsyncCache.AsMapView`), not BLC/ULC — a view change must cover it.
- Cross-check the collections testlibs (`:caffeine:googleTest`/`apacheTest`/`eclipseTest`,
  `:guava:test`) — they encode the contract's tolerated spread.

## Audit dimensions

1. **Map contract**: equals/hashCode consistency (and across map types — HashMap, TreeMap,
   IdentityHashMap), putAll atomicity (if weigher throws mid-batch), replaceAll per-entry
   atomicity, containsValue consistency.

2. **ConcurrentMap contract**: compute/computeIfAbsent/merge atomicity ("mapping function
   applied at most once"), getOrDefault on expired entries, forEach with concurrent
   mutations, compute returning null (should remove entry), the interface default methods.

3. **Null handling**: NPE at the correct points for null keys/values on *direct* ops;
   null *elements* in bulk args (`containsAll`/`removeAll`/`retainAll`) per the philosophy
   above; mapping functions returning null (compute→remove, merge→remove); putIfAbsent(key,
   null).

4. **Entry/EntrySet contracts**: Map.Entry.setValue() write-through, entrySet
   remove/contains checking both key AND value, snapshot vs live entries, entrySet().add UOE.

5. **Collection view contracts**: keySet()/values()/entrySet() backed by the cache
   (bidirectional), `contains`/`containsAll`/`remove`/`removeAll`/`retainAll`/`removeIf`,
   `containsAll(self)` short-circuit, view equals/hashCode over the logical (filtered) set.

6. **Cache semantics interaction**: expired-but-present entries visible via asMap()?
   Collected weak keys visible? asMap() operations triggering listeners? asMap().put() vs
   cache.put() differences (access time, stats, refresh)? Async raw view vs sync view.

For each finding: **quote the requirement from the fetched OpenJDK source** (the method +
actual body, not remembered JavaDoc), show Caffeine's behavior (ideally via the harness),
name the reference spread (CHM / CSLM / HashMap / Guava) and whether Caffeine is coherent,
and provide a test case. If Caffeine's divergence is *intentional and better*, the output is
a design-decisions.md / ledger note, not a bug.

