Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java project: an in-memory stock ledger with validated mutations

Last updated: 28 Sept 20263 min read
tutorial
IntermediateBy AITrove Editorial

An in-memory stock ledger owns item quantities and accepts changes only when each resulting balance remains within its declared range.

Java 8+. Use a JDK that supports this release.

Define the project boundaries

The ledger supports receive, dispatch, and read-only snapshot operations. Item IDs must be nonblank, quantities must be positive, dispatch cannot create a negative balance, and arithmetic overflow rejects the operation.

This is a runnable single-process project. It contains no database, authentication, concurrent writers, or event history. Restarting the program loses the ledger. Those limits belong in the project definition before anyone depends on it.

Use a TreeMap for predictable item ordering in the snapshot. That is a display contract, not a requirement that every stock system use sorted storage.

Reject before changing state

receive computes the checked new quantity before put. dispatch checks the current quantity before subtracting. A failed mutation leaves the existing balance intact.

The snapshot copies the map and then wraps the copy as unmodifiable. Returning a wrapper around the original map would expose later internal changes to an earlier reader. Integers and Strings keep this snapshot structurally and element-wise safe for this use.

Acceptance checks and next extensions

Run the program to receive ten units, dispatch three, attempt an excessive dispatch, and print the unchanged balance after rejection. Then add tests for unknown items, zero units, blank IDs, and overflow.

A persistence extension should commit changes transactionally. A concurrent extension needs one owner or an atomic operation across the balance rule. Adding a concurrent map alone does not make read-check-write atomic.

Working program

Java
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

public class StockLedgerProject {
    static final class Ledger {
        private final Map<String, Integer> quantities = new TreeMap<>();
        private void validate(String item, int units) {
            if (item == null || item.trim().isEmpty() || units <= 0) {
                throw new IllegalArgumentException("Item and positive units required");
            }
        }
        void receive(String item, int units) {
            validate(item, units);
            int updated = Math.addExact(quantities.getOrDefault(item, 0), units);
            quantities.put(item, updated);
        }
        void dispatch(String item, int units) {
            validate(item, units);
            int available = quantities.getOrDefault(item, 0);
            if (units > available) throw new IllegalArgumentException("Insufficient stock");
            quantities.put(item, available - units);
        }
        Map<String, Integer> snapshot() {
            return Collections.unmodifiableMap(new TreeMap<>(quantities));
        }
    }
    public static void main(String[] args) {
        Ledger ledger = new Ledger();
        ledger.receive("packing-tape", 10); ledger.dispatch("packing-tape", 3);
        Map<String, Integer> saved = ledger.snapshot();
        try { ledger.dispatch("packing-tape", 8); }
        catch (IllegalArgumentException expected) { System.out.println("rejected"); }
        ledger.receive("packing-tape", 2);
        System.out.println("saved=" + saved);
        System.out.println("current=" + ledger.snapshot());
    }
}

Output

Output
rejected
saved={packing-tape=7}
current={packing-tape=9}

Cost and design choices

Each map lookup or update costs O(log k) comparisons for k items. The snapshot copies k entries and requires O(k) new storage. Text comparison costs depend on item ID length.

Snapshots have a real cost. A large high-frequency reader may need another publication design, but that change must preserve a defined ownership and consistency contract.

Common Mistakes

  • Do not claim the ledger survives a restart.
  • Do not return a live internal map as a snapshot.
  • Do not use unsynchronized read-check-write with concurrent callers.

Connect the contracts

Review shared stock invariants before splitting the state across independently updated containers.

Compare the boundary explained in Boundary tests with the assumptions made by this program.

java
inventory-project
Storage details