List.copyOf returns an unmodifiable list with the source collection's elements at copy time, while retaining references to those elements.
List.copyOf freezes container membership but not mutable 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
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
1
sent
null rejectedCost 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.
