What is the difference between List.of(), Collections.unmodifiableList() and Arrays.asList()?

All three create lists you can't freely change, but in different ways. List.of() creates a truly immutable list. Collections.unmodifiableList() creates a read-only view of another list, which still changes if the original changes. Arrays.asList() creates a fixed-size list backed by an array: you can't add or remove elements, but you can replace them.

Key Points: • List.of() (Java 9): immutable, doesn't allow null elements, and throws UnsupportedOperationException on any change. • Collections.unmodifiableList(list): a view. Callers can't modify it, but changes to the underlying list show through. • List.copyOf(list) (Java 10): an immutable copy, unaffected by later changes to the source. • Arrays.asList(array): fixed size. add() and remove() throw, set() works and also changes the array. • Arrays.asList(int[]) gives a List<int[]> with one element, not a list of numbers. Use Integer[] or IntStream instead.

Code Example:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ImmutableLists {

    static void tryAdd(String name, List<String> list) {
        try {
            list.add("X");
            System.out.println(name + ": add worked");
        } catch (UnsupportedOperationException e) {
            System.out.println(name + ": add not allowed");
        }
    }

    public static void main(String[] args) {
        List<String> source = new ArrayList<>(List.of("A", "B"));
        List<String> view = Collections.unmodifiableList(source);
        List<String> copy = List.copyOf(source);

        tryAdd("List.of", List.of("A", "B"));
        tryAdd("unmodifiableList", view);

        source.add("C");                        // change the original
        System.out.println("View sees change: " + view);
        System.out.println("Copy unchanged: " + copy);

        String[] array = {"x", "y"};
        List<String> fixed = Arrays.asList(array);
        tryAdd("Arrays.asList", fixed);
        fixed.set(0, "z");                      // allowed, writes through to the array
        System.out.println("Array after set: " + Arrays.toString(array));

        int[] numbers = {1, 2, 3};
        System.out.println("Arrays.asList(int[]) size: " + Arrays.asList(numbers).size());
    }
}

Output:

List.of: add not allowed unmodifiableList: add not allowed View sees change: [A, B, C] Copy unchanged: [A, B] Arrays.asList: add not allowed Array after set: [z, y] Arrays.asList(int[]) size: 1

Good to Know: List.of() also rejects null: List.of("a", null) throws NullPointerException, and even list.contains(null) throws. This surprises people migrating from Arrays.asList(), which allows nulls.

Interview Tip: A concise interview answer is:

"List.of() creates a truly immutable list that rejects nulls. Collections.unmodifiableList() is only a read-only view, so changes to the original list still show through, which is why I use List.copyOf() when I need a real immutable copy. Arrays.asList() is a fixed-size list backed by the array: add and remove throw, but set works and changes the array."

Previous question: What is the difference between HashMap, Hashtable and ConcurrentHashMap?

Next question: How do you sort a list of objects by multiple fields using Comparator?

More Java Collections interview questions

All Java Collections questions