1. The copyOf methods: List.copyOf, Set.copyOf, Map.copyOf
Imagine a situation where a method must return a list, set, or map, and it’s important that the calling code cannot change the contents of that collection. For example, “a list of supported currencies” or “a set of user roles.” Any accidental insertion/deletion can break business logic.
Before Java 10 you had to either return a defensive copy manually or use wrappers like Collections.unmodifiableList(list). A wrapper protects only against changes through itself, not against modifications of the original collection.
Now we have a more reliable way — the copyOf methods.
What are they?
The methods List.copyOf(Collection), Set.copyOf(Collection), Map.copyOf(Map) (Java 10) create a truly immutable copy of the provided collection or map.
- The copy is not tied to the original collection: changes to the original do not affect the result of copyOf.
- You cannot modify the copy: any modification will result in UnsupportedOperationException.
Example of creating an immutable copy
import java.util.*;
public class CopyOfDemo {
public static void main(String[] args) {
List<String> modifiable = new ArrayList<>();
modifiable.add("Java");
modifiable.add("Python");
// Create an immutable copy
List<String> immutable = List.copyOf(modifiable);
// Try to modify the copy
try {
immutable.add("C++"); // Will throw UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("You cannot add an element to an immutable collection!");
}
// Modify the original list
modifiable.add("Kotlin");
// The copy remains unchanged!
System.out.println("Original list: " + modifiable);
System.out.println("Immutable copy: " + immutable);
}
}
Program output:
You cannot add an element to an immutable collection!
Original list: [Java, Python, Kotlin]
Immutable copy: [Java, Python]
Quick summary of each method
- List.copyOf(Collection) — returns an immutable list with the elements of the original collection.
- Set.copyOf(Collection) — returns an immutable set with the elements of the collection; duplicates will be removed.
- Map.copyOf(Map) — returns an immutable map with the pairs from the original map.
Example with Set and Map
import java.util.*;
public class CopyOfSetMapDemo {
public static void main(String[] args) {
Set<String> modifiableSet = new HashSet<>(Set.of("A", "B", "C"));
Set<String> immutableSet = Set.copyOf(modifiableSet);
// Try to add an element
try {
immutableSet.add("D");
} catch (UnsupportedOperationException e) {
System.out.println("You cannot modify Set!");
}
Map<String, Integer> modifiableMap = new HashMap<>();
modifiableMap.put("Alice", 30);
modifiableMap.put("Bob", 25);
Map<String, Integer> immutableMap = Map.copyOf(modifiableMap);
try {
immutableMap.put("Charlie", 28);
} catch (UnsupportedOperationException e) {
System.out.println("You cannot modify Map!");
}
}
}
2. Characteristics and constraints of the copyOf methods
Null elements are not allowed
Just like the factory methods List.of, Set.of, Map.of, the copyOf methods forbid null as an element, key, or value. If the original collection/map contains null, a NullPointerException will be thrown during copying.
List<String> listWithNull = Arrays.asList("A", null, "B");
List<String> immutable = List.copyOf(listWithNull); // Will throw NullPointerException!
The collection is truly immutable
Any attempt to add/remove/put/replace results in UnsupportedOperationException.
The original and the copy are not linked
Changes to the original collection do not affect the copy, and vice versa.
If the collection is already immutable — the same instance is returned
If you pass an already immutable collection (for example, the result of List.of(...)), copyOf returns the same object (memory saving).
List<String> immutable = List.of("X", "Y");
List<String> copy = List.copyOf(immutable);
System.out.println(immutable == copy); // true
Concrete implementation is not guaranteed
The type of the returned collection is just List, Set or Map. It is not necessarily ArrayList, HashSet or HashMap. Do not rely on implementation details (for example, the internal type and optimizations); the contract is immutability and the corresponding interface.
3. Difference between copyOf and wrappers (unmodifiable wrappers)
Collections.unmodifiableList(list) creates a wrapper over an existing collection. If the original collection changes, those changes are visible in the wrapper.
List.copyOf(list) creates a new immutable collection that is not linked to the original.
Demonstration of the difference
import java.util.*;
public class WrapperVsCopyOf {
public static void main(String[] args) {
List<String> original = new ArrayList<>(List.of("A", "B"));
// Wrapper
List<String> wrapper = Collections.unmodifiableList(original);
// Copy
List<String> copy = List.copyOf(original);
// Modify the original
original.add("C");
System.out.println("Wrapper: " + wrapper); // [A, B, C]
System.out.println("Copy: " + copy); // [A, B]
}
}
Output:
Wrapper: [A, B, C]
Copy: [A, B]
The wrapper reflects changes to the original, while the copy remains unchanged.
4. Practical scenarios for using copyOf
Protecting data when returning from a method
public class CurrencyService {
private final List<String> currencies = new ArrayList<>(List.of("USD", "EUR", "JPY"));
public List<String> getSupportedCurrencies() {
// No one will be able to modify the result
return List.copyOf(currencies);
}
}
Now the calling code cannot add or remove a currency from the returned list.
Passing data between application layers
When data is passed between layers (DAO → service → controller), use copyOf to be sure that no one will change the collection “along the way.”
Safe publication in multithreaded programs
Immutable collections are a convenient way to safely share data between threads without additional synchronization.
5. Common mistakes when using copyOf and immutable collections
Error #1: attempting to insert null. The copyOf methods (like of) do not tolerate null — neither as a list/set element nor as a map key/value. During copying you will get a NullPointerException.
Error #2: confusing a wrapper with a copy. copyOf creates an independent copy: changes to the original are not visible in the copy. Meanwhile, Collections.unmodifiableList(list) just wraps the original collection: all changes to the original will be reflected in the wrapper.
Error #3: trying to modify an immutable collection. Calls to add/remove/put on collections from copyOf/of result in UnsupportedOperationException.
Error #4: forgetting about map factory limitations. For example, Map.of() supports up to 10 key–value pairs. If you need more, use Map.ofEntries(...) or build a mutable map and then apply Map.copyOf.
Error #5: relying on a specific implementation. Do not expect copyOf to return exactly ArrayList or HashSet. Only the interface (List/Set/Map) and immutability are guaranteed.
GO TO FULL VERSION