Java Collections.unmodifiableCollection
Last modified: April 20, 2025
The Collections.unmodifiableCollection
method is a utility method
in Java's Collections Framework. It returns an unmodifiable view of the
specified collection. This view prevents modifications to the underlying
collection.
Unmodifiable collections are useful when you need to provide read-only access to data. They help enforce immutability and prevent accidental modifications. The original collection can still be modified if you maintain a reference to it.
Collections.unmodifiableCollection Overview
The unmodifiableCollection
method is part of the
java.util.Collections
class. It takes a collection as input and
returns an unmodifiable view of that collection. Any attempt to modify the
returned collection will throw an UnsupportedOperationException
.
This method is one of several unmodifiable collection wrappers in the Collections class. Similar methods exist for List, Set, Map, and other collection types. The returned view is live - changes to the original collection are visible through it.
Basic unmodifiableCollection Example
This example demonstrates the basic usage of
Collections.unmodifiableCollection
. We create a modifiable
ArrayList and then obtain an unmodifiable view of it. We show that the
original can still be modified but the view cannot.
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class BasicUnmodifiableCollection { public static void main(String[] args) { Collection<String> modifiable = new ArrayList<>(); modifiable.add("Apple"); modifiable.add("Banana"); Collection<String> unmodifiable = Collections.unmodifiableCollection(modifiable); System.out.println("Original collection: " + modifiable); System.out.println("Unmodifiable view: " + unmodifiable); // Modify original - change is visible in unmodifiable view modifiable.add("Cherry"); System.out.println("After adding to original: " + unmodifiable); try { // Attempt to modify unmodifiable view unmodifiable.add("Date"); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify unmodifiable collection: " + e); } } }
This code shows the fundamental behavior of unmodifiable collections. The original collection remains modifiable, while the unmodifiable view throws an exception on modification attempts. Changes to the original are reflected in the view.
The output demonstrates that adding to the original collection affects the unmodifiable view. However, attempting to modify the view directly results in an exception. This is the expected behavior.
Unmodifiable Collection with Different Types
The unmodifiableCollection
method works with any Collection
implementation. This example shows it being used with different collection
types: ArrayList, HashSet, and LinkedList. The behavior is consistent
across all implementations.
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; public class DifferentCollectionTypes { public static void main(String[] args) { // ArrayList example Collection<String> arrayList = new ArrayList<>(); arrayList.add("Red"); arrayList.add("Green"); Collection<String> unmodArrayList = Collections.unmodifiableCollection(arrayList); System.out.println("ArrayList unmodifiable: " + unmodArrayList); // HashSet example Collection<Integer> hashSet = new HashSet<>(); hashSet.add(10); hashSet.add(20); Collection<Integer> unmodHashSet = Collections.unmodifiableCollection(hashSet); System.out.println("HashSet unmodifiable: " + unmodHashSet); // LinkedList example Collection<Double> linkedList = new LinkedList<>(); linkedList.add(3.14); linkedList.add(2.71); Collection<Double> unmodLinkedList = Collections.unmodifiableCollection(linkedList); System.out.println("LinkedList unmodifiable: " + unmodLinkedList); // All throw UnsupportedOperationException on modification try { unmodArrayList.add("Blue"); } catch (UnsupportedOperationException e) { System.out.println("ArrayList modification failed as expected"); } } }
This example demonstrates that unmodifiableCollection
works
uniformly across different Collection implementations. The method doesn't
care about the specific implementation - it works with any Collection.
The output shows that regardless of whether the underlying collection is an ArrayList, HashSet, or LinkedList, the unmodifiable view behaves the same way. All modification attempts result in exceptions.
Defensive Copy vs Unmodifiable View
This example compares two approaches to protecting collections from modification: creating a defensive copy versus using an unmodifiable view. Each approach has different characteristics and use cases.
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class DefensiveCopyVsUnmodifiable { public static void main(String[] args) { Collection<String> original = new ArrayList<>(); original.add("One"); original.add("Two"); // Approach 1: Defensive copy Collection<String> defensiveCopy = new ArrayList<>(original); // Approach 2: Unmodifiable view Collection<String> unmodifiableView = Collections.unmodifiableCollection(original); System.out.println("Original: " + original); System.out.println("Defensive copy: " + defensiveCopy); System.out.println("Unmodifiable view: " + unmodifiableView); // Modify original original.add("Three"); System.out.println("\nAfter modifying original:"); System.out.println("Original: " + original); System.out.println("Defensive copy unchanged: " + defensiveCopy); System.out.println("Unmodifiable view reflects change: " + unmodifiableView); } }
This code highlights the key difference between defensive copies and unmodifiable views. A defensive copy is a completely separate collection that doesn't reflect changes to the original. An unmodifiable view is a live view that shows changes to the original collection.
The output demonstrates that the defensive copy remains unchanged when the original is modified, while the unmodifiable view shows the new elements. Choose based on whether you want isolation or live updates.
Unmodifiable Collection in Method Return
A common use case for unmodifiable collections is returning them from methods. This example shows how to safely expose internal collections without allowing external modification. The technique helps maintain encapsulation.
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; class Team { private Collection<String> members = new ArrayList<>(); public Team() { members.add("Alice"); members.add("Bob"); members.add("Charlie"); } // Safe way to expose the collection public Collection<String> getMembers() { return Collections.unmodifiableCollection(members); } // Controlled modification public void addMember(String name) { members.add(name); } } public class MethodReturnExample { public static void main(String[] args) { Team team = new Team(); Collection<String> teamMembers = team.getMembers(); System.out.println("Team members: " + teamMembers); // Attempt to modify - will throw exception try { teamMembers.add("Diana"); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify team members directly"); } // Proper way to add a member team.addMember("Diana"); System.out.println("After proper addition: " + team.getMembers()); } }
This example demonstrates a good practice for exposing collections from classes. Instead of returning the internal collection directly, we return an unmodifiable view. This prevents external code from modifying our internal state directly.
The output shows that attempts to modify the returned collection fail, while proper modification through the class's methods works. This maintains control over the collection's state.
Nested Unmodifiable Collections
This example explores what happens when working with nested collections. Making the outer collection unmodifiable doesn't automatically make contained collections unmodifiable. We need to handle nesting carefully.
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class NestedCollections { public static void main(String[] args) { Collection<Collection<String>> nested = new ArrayList<>(); Collection<String> inner1 = new ArrayList<>(); inner1.add("A"); inner1.add("B"); Collection<String> inner2 = new ArrayList<>(); inner2.add("X"); inner2.add("Y"); nested.add(inner1); nested.add(inner2); Collection<Collection<String>> unmodifiableNested = Collections.unmodifiableCollection(nested); System.out.println("Original nested: " + nested); System.out.println("Unmodifiable nested: " + unmodifiableNested); // Can't modify outer collection try { unmodifiableNested.add(new ArrayList<>()); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify outer collection"); } // But can modify inner collections! inner1.add("C"); System.out.println("After inner modification: " + unmodifiableNested); } }
This code demonstrates that unmodifiable collections only protect one level of the collection structure. While we can't add or remove inner collections from the outer collection, we can still modify the inner collections themselves.
The output shows that changes to the inner collections are reflected in the unmodifiable view. For complete immutability, all nested collections would need to be made unmodifiable as well.
Performance Considerations
This example examines the performance characteristics of unmodifiable collections. We compare operations on regular collections versus their unmodifiable views. The overhead is minimal for read operations.
package com.zetcode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; public class PerformanceComparison { public static void main(String[] args) { final int SIZE = 1_000_000; final int ITERATIONS = 100; Collection<Integer> largeList = new ArrayList<>(); for (int i = 0; i < SIZE; i++) { largeList.add(i); } Collection<Integer> unmodifiable = Collections.unmodifiableCollection(largeList); // Test iteration performance long start = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { for (Integer num : largeList) { // Just iterate } } long end = System.currentTimeMillis(); System.out.println("Regular collection iteration: " + (end - start) + "ms"); start = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { for (Integer num : unmodifiable) { // Just iterate } } end = System.currentTimeMillis(); System.out.println("Unmodifiable iteration: " + (end - start) + "ms"); // Test contains performance start = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { largeList.contains(SIZE / 2); } end = System.currentTimeMillis(); System.out.println("Regular contains: " + (end - start) + "ms"); start = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { unmodifiable.contains(SIZE / 2); } end = System.currentTimeMillis(); System.out.println("Unmodifiable contains: " + (end - start) + "ms"); } }
This performance test shows that read operations on unmodifiable collections have negligible overhead compared to regular collections. The unmodifiable wrapper doesn't add significant performance costs for operations like iteration or contains checks.
The output demonstrates that iteration and search times are nearly identical between the regular and unmodifiable collections. The unmodifiable wrapper is a lightweight view that doesn't copy data.
Source
Java Collections.unmodifiableCollection Documentation
In this article, we've explored the Collections.unmodifiableCollection
method in depth. We've covered basic usage, different collection types,
performance characteristics, and common patterns. Understanding unmodifiable
collections is essential for writing robust Java code.
Author
List all Java tutorials.