How to create an immutable collection
Short answer
The factory methods List.of(), Set.of() and Map.of() create collections that cannot be changed:
void main() {
List<String> roles = List.of("admin", "editor");
Map<String, Integer> limits = Map.of("free", 10, "pro", 100);
IO.println(roles);
IO.println(limits.get("pro"));
}Output:
[admin, editor]
100A copy of an existing collection
List.copyOf() makes an unmodifiable copy. Later changes to the original do not affect it:
void main() {
var names = new ArrayList<>(List.of("Anna"));
var snapshot = List.copyOf(names);
names.add("Oleg");
IO.println(names);
IO.println(snapshot);
}Output:
[Anna, Oleg]
[Anna]A read-only view
Collections.unmodifiableList() does not copy the data, it only forbids changes through itself. Changes to the original are visible in it:
void main() {
var names = new ArrayList<>(List.of("Anna"));
var view = Collections.unmodifiableList(names);
names.add("Oleg");
IO.println(view);
}Output:
[Anna, Oleg]From a stream
A stream's toList() also returns an unmodifiable list:
void main() {
var evens = Stream.of(1, 2, 3, 4).filter(n -> n % 2 == 0).toList();
IO.println(evens);
evens.add(6);
}Output:
[2, 4]
Exception in thread "main" java.lang.UnsupportedOperationExceptionCommon mistake
Assuming the elements are immutable too. The collection does not let you add or remove elements, but a mutable object inside can still be changed:
void main() {
var lists = List.of(new ArrayList<>(List.of(1)));
lists.getFirst().add(2);
IO.println(lists);
}Output:
[[1, 2]]For full immutability the elements must be immutable too — for example, records with strings and numbers.
Functions used in this recipe
- List.of() — Creates an unmodifiable list from the listed elements.
- List.copyOf() — Creates an unmodifiable list copy of a collection.
- Collections.unmodifiableList() — Returns a view of a list that cannot be modified.
- Map.of() — Creates an unmodifiable map from key and value pairs.