Java Collections.unmodifiableList
Last modified: April 20, 2025
The Collections.unmodifiableList
method is part of Java's
Collections Framework. It provides a way to create immutable (unmodifiable)
views of lists. This is useful when you need to share a list but prevent
modifications.
An unmodifiable list is a wrapper around an existing list that throws
UnsupportedOperationException
on modification attempts. The
original list can still be modified, and changes will be visible through
the unmodifiable view.
Basic Definitions
An unmodifiable list is a read-only view of a list that cannot be modified structurally. Structural modifications include adding, removing, or replacing elements. The list's contents can still be accessed and iterated over normally.
The Collections.unmodifiableList
method takes a List
as input and returns an unmodifiable view of that list. The returned list
implements all list operations but throws exceptions on modification attempts.
Creating an Unmodifiable List
This example demonstrates the basic usage of Collections.unmodifiableList
.
We create a mutable ArrayList, then obtain an unmodifiable view of it. The
example shows both successful read operations and failed modification attempts.
package com.zetcode; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class BasicUnmodifiableList { public static void main(String[] args) { List<String> mutableList = new ArrayList<>(); mutableList.add("Apple"); mutableList.add("Banana"); mutableList.add("Cherry"); // Create unmodifiable view List<String> unmodifiableList = Collections.unmodifiableList(mutableList); // Can read elements System.out.println("First element: " + unmodifiableList.get(0)); System.out.println("Size: " + unmodifiableList.size()); try { // Attempt to modify unmodifiableList.add("Orange"); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify: " + e.getMessage()); } // Original can still be modified mutableList.add("Orange"); System.out.println("Updated view: " + unmodifiableList); } }
This code shows how to create and use an unmodifiable list. We first create a
mutable list and populate it with elements. Then we create an unmodifiable view
using Collections.unmodifiableList
.
The example demonstrates that while we can't modify the unmodifiable view directly, changes to the original list are reflected in the view. This makes unmodifiable lists ideal for providing read-only access to internal data.
Defensive Copy vs Unmodifiable View
This example compares creating a defensive copy of a list versus using an unmodifiable view. A defensive copy creates a new independent list, while an unmodifiable view is just a wrapper around the original.
package com.zetcode; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class CopyVsUnmodifiable { public static void main(String[] args) { List<String> original = new ArrayList<>(); original.add("One"); original.add("Two"); // Defensive copy List<String> copy = new ArrayList<>(original); // Unmodifiable view List<String> unmodifiable = Collections.unmodifiableList(original); // Modify original original.add("Three"); System.out.println("Original: " + original); System.out.println("Copy: " + copy); System.out.println("Unmodifiable view: " + unmodifiable); } }
This example highlights the difference between a defensive copy and an unmodifiable view. The defensive copy is completely independent of the original list, while the unmodifiable view reflects changes to the original list.
The output shows that adding "Three" to the original list doesn't affect the copy but is visible in the unmodifiable view. Choose between these approaches based on whether you need isolation or just read-only access.
Nested Unmodifiable Lists
When working with nested lists, creating an unmodifiable outer list doesn't make the inner lists unmodifiable. This example demonstrates this behavior and shows how to create a deeply unmodifiable structure.
package com.zetcode; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class NestedUnmodifiableLists { public static void main(String[] args) { Listnested = new ArrayList<>(); nested.add(new ArrayList<>(List.of("A", "B"))); nested.add(new ArrayList<>(List.of("C", "D"))); // Create unmodifiable view of outer list List
unmodifiableOuter = Collections.unmodifiableList(nested); // Can't modify outer list try { unmodifiableOuter.add(new ArrayList<>()); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify outer list"); } // Can still modify inner lists unmodifiableOuter.get(0).add("X"); System.out.println("Modified inner list: " + unmodifiableOuter); // Create deeply unmodifiable structure List
deeplyUnmodifiable = nested.stream() .map(Collections::unmodifiableList) .toList(); try { deeplyUnmodifiable.get(0).add("Y"); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify inner lists either"); } } }
This example shows that Collections.unmodifiableList
only makes
the outer list unmodifiable. The inner lists remain mutable unless explicitly
made unmodifiable. We demonstrate both scenarios.
The solution for creating a deeply unmodifiable structure uses Java streams
to apply unmodifiableList
to each inner list, then collects them
into an immutable outer list. This provides complete immutability.
Unmodifiable List from Java 9 List.of
Java 9 introduced the List.of
factory methods that create
truly immutable lists. This example compares them with
Collections.unmodifiableList
and shows their differences.
package com.zetcode; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ListOfVsUnmodifiable { public static void main(String[] args) { // Java 9+ immutable list List<String> listOf = List.of("Red", "Green", "Blue"); // Traditional unmodifiable view List<String> mutable = new ArrayList<>(); mutable.add("Red"); mutable.add("Green"); mutable.add("Blue"); List<String> unmodifiable = Collections.unmodifiableList(mutable); // Both throw UnsupportedOperationException on modification try { listOf.add("Yellow"); } catch (UnsupportedOperationException e) { System.out.println("listOf is immutable"); } try { unmodifiable.add("Yellow"); } catch (UnsupportedOperationException e) { System.out.println("unmodifiable is unmodifiable"); } // Difference: listOf is truly immutable try { listOf.set(0, "Pink"); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify listOf at all"); } // unmodifiable reflects changes to original mutable.set(0, "Pink"); System.out.println("unmodifiable shows changes: " + unmodifiable); } }
This example highlights key differences between List.of
and
Collections.unmodifiableList
. While both prevent modifications,
List.of
creates a truly immutable list with no backing mutable
source.
The Collections.unmodifiableList
is just a view that reflects
changes to the original list. List.of
is generally preferred for
creating immutable lists when the data is fixed and known upfront.
Performance Considerations
This example examines the performance characteristics of unmodifiable lists compared to regular lists. We measure the time taken for various operations to understand the overhead.
package com.zetcode; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class UnmodifiableListPerformance { public static void main(String[] args) { final int SIZE = 1_000_000; List<Integer> mutable = new ArrayList<>(SIZE); for (int i = 0; i < SIZE; i++) { mutable.add(i); } // Create unmodifiable view List<Integer> unmodifiable = Collections.unmodifiableList(mutable); // Measure iteration time long start = System.nanoTime(); for (int num : unmodifiable) { // Just iterate } long end = System.nanoTime(); System.out.printf("Iteration time: %d ms%n", (end - start) / 1_000_000); // Measure get operation start = System.nanoTime(); for (int i = 0; i < 1000; i++) { unmodifiable.get(i); } end = System.nanoTime(); System.out.printf("Get operations: %d ns per get%n", (end - start) / 1000); // Measure size operation start = System.nanoTime(); for (int i = 0; i < 1000; i++) { unmodifiable.size(); } end = System.nanoTime(); System.out.printf("Size operations: %d ns per size%n", (end - start) / 1000); } }
This performance test shows that unmodifiable lists have minimal overhead for read operations. The wrapper delegates all read operations to the underlying list, so performance is nearly identical to the original mutable list.
The measurements demonstrate that iteration, element access, and size checking are just as fast with unmodifiable lists. The only performance impact comes from the additional method call indirection, which is negligible in most cases.
Thread Safety with Unmodifiable Lists
While unmodifiable lists prevent modification through the view, they don't automatically make the list thread-safe. This example demonstrates thread safety considerations when using unmodifiable lists.
package com.zetcode; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class UnmodifiableListThreadSafety { public static void main(String[] args) throws InterruptedException { List<String> sharedList = new ArrayList<>(); sharedList.add("Initial"); // Create unmodifiable view List<String> unmodifiable = Collections.unmodifiableList(sharedList); // Thread that reads from unmodifiable view Thread reader = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println("Reader sees: " + unmodifiable); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); // Thread that modifies original list Thread writer = new Thread(() -> { for (int i = 1; i <= 3; i++) { sharedList.add("Update " + i); try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); reader.start(); writer.start(); reader.join(); writer.join(); System.out.println("Final state: " + unmodifiable); } }
This example demonstrates that while the unmodifiable view itself is thread-safe for reading, the underlying list may still be modified by other threads. The reader thread sees the changes made by the writer thread through the unmodifiable view.
If true thread safety is required, consider using CopyOnWriteArrayList
or synchronizing access to the original list. The unmodifiable wrapper only
prevents modification through that specific reference, not through other
references to the original list.
Real-world Use Case: API Design
This example shows a practical use of Collections.unmodifiableList
in API design, where we want to expose internal data without allowing
modifications.
package com.zetcode; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class UnmodifiableListAPI { private final List<String> internalData = new ArrayList<>(); public UnmodifiableListAPI() { internalData.add("Data1"); internalData.add("Data2"); } /** * Returns an unmodifiable view of the internal data. * Clients can read but not modify the list. */ public List<String> getData() { return Collections.unmodifiableList(internalData); } // Internal method to modify the data public void addData(String item) { internalData.add(item); } public static void main(String[] args) { UnmodifiableListAPI api = new UnmodifiableListAPI(); // Get unmodifiable view List<String> data = api.getData(); System.out.println("Initial data: " + data); // Try to modify (will fail) try { data.add("Data3"); } catch (UnsupportedOperationException e) { System.out.println("Cannot modify returned list"); } // Modify through internal method api.addData("Data3"); System.out.println("Updated data: " + api.getData()); } }
This example demonstrates using Collections.unmodifiableList
in a
class that exposes internal data through a public API. The getData
method returns an unmodifiable view, preventing clients from modifying the
internal list directly.
The example shows that attempts to modify the returned list fail, but the class can still update its internal data through its own methods. This pattern is common in API design to protect internal state while allowing read access.
Source
Java Collections.unmodifiableList Documentation
In this tutorial, we've explored Collections.unmodifiableList
in depth.
We've covered basic usage, defensive copies versus unmodifiable views, handling
nested lists, comparisons with Java 9's List.of
, performance
considerations, thread safety, and a practical API design use case. This method
is valuable for creating read-only views of lists, particularly in scenarios
where data needs to be shared safely.
Author
List all Java tutorials.