Java harness reliability / six-guide learning path · Guide 4 of 6. Begin with the stated failure and test the resource or state boundary before changing concurrency or JVM settings.
Separate visibility, consistency and durability
A harness writes a checkpoint after each synthetic tool observation. A reader uses the latest published state to render progress or resume work. Three questions often get collapsed into one: can the reader see a new reference, does that reference describe one stable version, and will that version survive process failure? A concurrent collection or atomic reference cannot answer all three questions by itself.
This guide addresses in-memory publication of a stable version. AtomicReference get and set provide the documented volatile-style memory effects for the reference. Publishing a reference does not freeze the object graph reachable from it. A disk-backed checkpoint store needs a separate persistence protocol, including what counts as a committed version and how recovery distinguishes complete from partial writes. No disk durability is claimed by the example.
- STEP 1Build an isolated snapshot
- STEP 2Atomically publish one reference
- STEP 3Reject a stale writer version
Copy the data reachable from the checkpoint
Suppose State is a record with a version number and a map of token lists. A final map field prevents assigning a different map to that field after construction; it does not make the map or its values immutable. Likewise, an unmodifiable wrapper can still expose later changes in the backing collection. The reader needs a snapshot with the intended ownership semantics, not merely an API that rejects put.
The Java fixture copies every nested list with List.copyOf, then creates an unmodifiable map with Map.copyOf. Its elements are Strings, so this copying reaches the mutable boundary used by the exercise. For arbitrary domain objects, copying a list is still shallow with respect to those elements. Define an immutable representation or copy the relevant elements as well. Also validate null and size rules before treating a failed copy as a retriable storage problem.
Publish a whole version in one transition
Build the next State away from readers, validate it, then publish its reference. Avoid updating the version field, observation list and completion status through separate visible mutations. A reader should capture one State reference and make all related decisions from that reference. Reading several independently changing fields can combine data from different logical versions even if individual field reads are thread-safe.
When more than one writer can propose an update, compare-and-set can reject a proposal based on an obsolete reference. The fixture publishes version two, then proves that a writer still expecting the original reference cannot replace it. This is a local concurrency check. A distributed store needs its own conditional write or transaction. An integer version in a Java record alone does not coordinate processes.
Keep retryable state functions free of external effects
An atomic update loop may need to recompute its proposal when another writer wins. If the computation sends a tool request, emits an external message or increments a remote counter, recomputing it can repeat the effect. Keep the state transformation pure and handle effects through an explicit operation identity and lifecycle. The API's update-function contract is not an effect-delivery protocol.
For a harness that permits exactly one writer per run, a simpler ownership rule can be preferable to unrestricted concurrent updates. Children can return observations to a coordinator that validates and serializes state transitions. That reduces the number of interleavings to explain. It does not excuse mutable aliases in the coordinator's published objects, because an old reader can still retain an earlier reference.
Test old readers and stale writers separately
The browser lab captures a reader, mutates the working state, and shows whether the reader changes. With an outer-only copy, the version can remain one while its token list grows. That mismatch is the useful failure case: a stable version label is not proof of a stable snapshot. Copying both the outer object and the nested list avoids the demonstrated alias.
The Java fixture tests a different invariant: nested data stays stable after the working list changes, and compare-and-set rejects a stale proposal. Add application-specific tests for payload limits, ordering of observations and recovery from an interrupted write in the real store. A passing in-memory fixture establishes neither serialization compatibility nor durable resumption. Keep those checks named separately in the release record.
Run the Java boundary check
Use a JDK with Java 21 APIs or later. Save the original SnapshotDemo.java into an empty local directory. The example uses only the JDK and synthetic fixtures; no model, network service or credentials are required.
javac --release 21 SnapshotDemo.java
java -ea SnapshotDemo// Author: Sandeep Belgavi. In-memory publication; not a durable checkpoint store.
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class SnapshotDemo {
record State(int version, Map<String, List<String>> observations) {
State {
var copy = new HashMap<String, List<String>>();
observations.forEach((key, value) -> copy.put(key, List.copyOf(value)));
observations = Map.copyOf(copy); // Elements are immutable Strings.
}
}
public static void main(String[] args) {
var working = new ArrayList<>(List.of("seed"));
var initial = new State(1, Map.of("tokens", working));
var published = new AtomicReference<>(initial);
var reader = published.get();
working.add("changed-later");
if (!reader.observations().get("tokens").equals(List.of("seed")))
throw new AssertionError("Mutable alias escaped");
var next = new State(2, Map.of("tokens", working));
if (!published.compareAndSet(initial, next)) throw new AssertionError("Update lost");
boolean staleAccepted = published.compareAndSet(initial,
new State(3, Map.of("tokens", List.of("stale"))));
if (staleAccepted || reader.version() != 1 || published.get().version() != 2)
throw new AssertionError("Version publication invariant failed");
System.out.println("old_snapshot_stable=true current_version=2 stale_writer_rejected=true");
}
}
Compatibility check: the examples were compiled with --release 21 and executed on local HotSpot JDK 23.0.2. This checks the demonstrated invariants on that runtime; it is not a JDK performance comparison. APIs and diagnostic descriptions are linked to the Java 25 documentation, accessed 30 October 2026. No preview API is required.
Try the companion browser lab
Open Checkpoint aliases: watch a published snapshot change. Predict whether copying only the outer object protects a reader's nested token list. Then compare outer copying, nested copying and sharing the original object.
Expected outcome: Copying both levels keeps the captured reader at version one with the seed token. Reusing the nested list changes the reader's tokens even when its version stays one. Sharing the whole object changes both.
Scope: This is an explicit aliasing/interleaving model, not a Java Memory Model simulator. It does not demonstrate atomic-reference ordering, compare-and-set retries, disk persistence or distributed ownership. The Java fixture checks a separate in-memory CAS example.
Primary sources and evidence limits
- AtomicReference memory effects
- Map.copyOf and unmodifiable maps
- Concurrent utilities and memory consistency
The scenarios, diagrams, arithmetic and code are original educational examples. Browser ticks are synthetic. Java fixture success is not evidence of production throughput, latency, memory use or model quality.
Views are my own and do not represent any employer, past or present.
