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

Java collection factories: rejected updates and shallow element ownership

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

List.of and List.copyOf create lists that reject structural updates; they do not recursively freeze the objects stored inside the list.

Java 11+. This is a complete program using JDK classes.

Snapshot the right layer

A receipt export should not gain another row when its source list changes. List.copyOf supplies a membership snapshot for that boundary. It can reuse an already suitable unmodifiable list, so code should depend on its contract rather than whether a different wrapper object was allocated.

The copied membership can still contain mutable row objects. Changing a field on one of those rows changes what both references see. Either publish immutable row values, copy each row, or document shared ownership. The outer list alone cannot make that choice.

These factories reject null entries. A nullable input from an older API therefore needs an explicit rejection or conversion step. Arrays.asList has a different contract: fixed size, but element replacement through set is allowed and is backed by the original array.

Version the boundary

List.of is available from Java 9, while List.copyOf is available from Java 10. This program targets Java 11 so both calls belong to the stated toolchain. A Java 8 project can use a copied ArrayList plus Collections.unmodifiableList, with the same shallow-copy warning.

Working program

Java
import java.util.ArrayList;
import java.util.List;
public class ReceiptMembershipSnapshot {
    public static void main(String[] args) {
        List<StringBuilder> drafts = new ArrayList<>();
        drafts.add(new StringBuilder("pending"));
        List<StringBuilder> exported = List.copyOf(drafts);
        drafts.add(new StringBuilder("received"));
        drafts.get(0).append(":reviewed");
        System.out.println(exported.size());
        System.out.println(exported.get(0));
        try { exported.add(new StringBuilder("late")); }
        catch (UnsupportedOperationException rejected) { System.out.println("Membership fixed"); }
        try { List.of("ready", null); }
        catch (NullPointerException rejected) { System.out.println("Null rejected"); }
    }
}

Output

Output
1
pending:reviewed
Membership fixed
Null rejected

Costs and boundaries

Copying a mutable input of n members takes O(n) reference work and O(n) membership storage. Element content is not copied by this operation. Do not assume a new allocation for every copyOf invocation; do assume the documented update restrictions.

Common Mistakes

  • Unmodifiable membership is not deep immutability.
  • Do not treat Arrays.asList as the same API.
  • Null rejection must be tested before publishing imported rows.

Read next

Backed views, Value records.

Apply this contract in Spring

Spring API pagination: bounded requests and immutable snapshots. These lessons keep framework assembly separate from the Java contract.

java
immutable-collections
Storage details