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

List.copyOf freezes container membership but not mutable elements

Last updated: 1 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

List.copyOf returns an unmodifiable list with the source collection's elements at copy time, while retaining references to those elements.

Separate container snapshot from object snapshot

Appending a new shipment record to the source list does not append it to the copied list. Mutating a ShipmentRecord already present changes the status observed through both lists because both hold the same object. This distinction matters for cached responses, audit trails, and cross-thread handoffs.

Null elements are rejected. A copy operation can also avoid a fresh allocation when its input is already an unmodifiable list, so code must not rely on a distinct list identity. Its content contract matters more than whether the returned reference is new.

Choose the needed isolation level

For an independent mutable container, use a new ArrayList. For immutable record values, a shallow container copy may be enough. For mutable nested objects, create purpose-built value snapshots before List.copyOf. Record defensive copies address a related boundary for array fields.

Collections.unmodifiableList leaves membership live through the backing collection. These two APIs solve different ownership problems despite both rejecting edits through the returned reference.

Working program

Java
import java.util.ArrayList;
import java.util.List;

public class DispatchContainerSnapshot {
    static final class ShipmentRecord {
        final String dispatchId;
        String status;
        ShipmentRecord(String dispatchId, String status) {
            this.dispatchId = dispatchId; this.status = status;
        }
    }
    public static void main(String[] args) {
        ShipmentRecord first = new ShipmentRecord("load-15", "queued");
        List<ShipmentRecord> source = new ArrayList<>();
        source.add(first);
        List<ShipmentRecord> snapshot = List.copyOf(source);
        source.add(new ShipmentRecord("seal-47", "queued"));
        first.status = "sent";
        System.out.println(snapshot.size());
        System.out.println(snapshot.get(0).status);
        try { List.copyOf(java.util.Arrays.asList("ship-82", null)); }
        catch (NullPointerException rejected) { System.out.println("null rejected"); }
    }
}

Output

Output
1
sent
null rejected

Cost and ownership

Copying n element references costs O(n) traversal and generally O(n) container storage, though an implementation may reuse a suitable immutable input. Mutable element objects are shared; copying the container alone does not isolate their state.

Common Mistakes

  • Do not call a shallow container snapshot a deep copy.
  • Do not assume List.copyOf accepts null elements.
  • Do not rely on a fresh returned object identity.

Read next

Collections.unmodifiableList is a live view, not a snapshot, Java array copies: new slots can still point at old objects, Java records with arrays: copy on input and output, Arrays.asList in Java: fixed-size list backed by the original array.

java
collections
list-copyof-element-alias
Storage details