Java

Lesson 02

Immutable data and defensive copies

Protect domain snapshots from outside mutation by copying collections and exposing immutable views.

Good Code

src/main/java/dev/review/orders/OrderSnapshot.java
public record OrderSnapshot(OrderId id, List<LineItem> items) {
    public OrderSnapshot {
        Objects.requireNonNull(id, "id");
        items = List.copyOf(items);
    }

    public Money total() {
        return items.stream()
            .map(LineItem::subtotal)
            .reduce(Money.zero(), Money::plus);
    }
}

Bad Code

src/main/java/OrderSnapshot.java
public class OrderSnapshot {
    private final List<LineItem> items;

    public OrderSnapshot(List<LineItem> items) {
        this.items = items;
    }

    public List<LineItem> items() {
        return items;
    }
}

Review Notes

What to review

Good Code

The good version copies the collection at construction time, so later caller mutation cannot change the snapshot. Its fields are final through the record shape.

Bad Code

The bad version stores and returns the same mutable list. Any code with a reference can add, remove, or reorder items after the object was created.

Takeaways

  • Objects that represent a completed decision should not change because a caller still holds a mutable collection reference.