Immutable collections
Immutable, unmodifiable and fixed-size collection variants, factory methods, stream collectors, UnsupportedOperationException, shallow immutability and defensive copies.
On this page
🧊 What are immutable collections?
An immutable collection is a collection whose membership cannot change after it is created: no add, no remove, no set, no hidden backing list that can later mutate it. It acts like a printed menu: customers can read it all day, but changing tonight's specials means printing a new menu. In Java, List.of("standard", "express") is immutable; Collections.unmodifiableList(source) is only a read-only window over source.
For this lesson, we'll focus on the three tiers you need in interviews and code review: unmodifiable views, unmodifiable copies and Java's immutable factory collections. We'll also cover Arrays.asList, UnsupportedOperationException, immutable map implementations, stream collectors, shallow immutability, defensive copies and performance notes. Interviewers love this one because the words sound interchangeable until a production object changes behind your back.
Below the line (out of scope)
- Designing fully immutable objects and records: Designing immutable objects
finalreferences versus immutable objects: The final keyword- Array/list/set/map conversion recipes: Converting between arrays, lists, sets and maps
- Utility wrappers in the wider toolbox: Collections and Arrays utility methods
The 30-second answer
There are three different things people call immutable. Collections.unmodifiableList(list) is an unmodifiable view: writes through the view throw, but changes to the backing list show through. List.copyOf(list) is an unmodifiable copy: it snapshots the elements, rejects null, and may return the same instance if the input is already one of the JDK's immutable lists. List.of, Set.of, Map.of and Map.ofEntries are Java 9 immutable factory collections: no nulls, duplicate set elements or map keys throw IllegalArgumentException, and Map.of has overloads up to 10 key-value pairs. Arrays.asList is neither resizable nor immutable: set works, add throws. Stream toList() is unmodifiable and allows nulls; Collectors.toList() gives no mutability guarantee; Collectors.toUnmodifiableList() rejects nulls. All of these are shallow, so mutable elements can still change.
What you’ll learn
- Separate unmodifiable views, unmodifiable copies and true immutable factory collections
- Explain why
Collections.unmodifiableListcan still change when its backing list changes - Debug
Java List UnsupportedOperationExceptionfromArrays.asList,List.of,Stream.toListand wrapper collections - Use Java convenience factory methods:
List.of,Set.of,Map.ofandMap.ofEntries - Collect a Java Stream to an immutable collection with
toList,toUnmodifiableListandcopyOf - Write defensive copies in constructors and getters, and remember immutable collections are shallow
Interview radar
Must-know: Why does add throw UnsupportedOperationException on a list I can still set?
Core follow-up: What changes if the original list is modified after Collections.unmodifiableList versus List.copyOf?
Production angle: A supposedly immutable config object changes because its constructor kept the caller's mutable ArrayList.
01Picture it first: window, snapshot, printed menu
Let's get the picture right first. An unmodifiable view is a window into someone else's room: you cannot move furniture through the window, but if the owner moves a chair, you see it. An unmodifiable copy is a photo taken once. An immutable factory collection is closer to a printed menu built for sharing.
A mutable source list feeds three variants: an unmodifiable view that still sees source changes, a copy that does not, and an immutable factory list that has no source
Notice the source arrow. The view keeps pointing at the original collection. The copy and factory collection do not, although they still point at the same element objects unless you copy those too.
02Three tiers under the hood: view, copy, factory
The simple option is a wrapper. It stops writes through your reference and is cheap to create. It stops being enough when the original collection can still be mutated somewhere else, which is most API boundaries.
Try it yourself first
Before reading the output, decide which lists print the new "refund" value after source.add.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ImmutableTiersDemo {
public static void main(String[] args) {
List<String> source = new ArrayList<>(List.of("card", "cash"));
List<String> view = Collections.unmodifiableList(source);
List<String> copy = List.copyOf(source);
List<String> factory = List.of("card", "cash");
source.add("refund");
System.out.println(view); // [card, cash, refund]
System.out.println(copy); // [card, cash]
System.out.println(factory); // [card, cash]
try {
view.add("wire");
} catch (UnsupportedOperationException expected) {
System.out.println("view rejects writes through itself");
}
}
}
Notice three things:
viewis read-only, not independent. It delegates reads tosource.copyis independent from later structural changes tosource, but it is still shallow.factorynever had a mutable source in the first place.
List.copyOf, Set.copyOf and Map.copyOf were added in Java 10. If the input is already one of the JDK's immutable collections, copyOf is allowed to return the same instance. That is good: copying an immutable value again buys nothing.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Advanced: factory methods, UnsupportedOperationException and streams
- Shallow immutability and defensive copies
- Comparing the variants
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal
Further reading (9)
- Java 21 API: java.util.List
- Java 21 API: java.util.Set
- Java 21 API: java.util.Map
- Java 21 API: java.util.Collections
- Java 21 API: java.util.stream.Stream
- Java 21 API: java.util.stream.Collectors
- JEP 269: Convenience Factory Methods for Collections
- OpenJDK ImmutableCollections source
- Guava Immutable Collections Explained