Difference between Arrays.asList, List.of, and Collection… — Cracked Java
// Java Collections Framework · List, ArrayList, LinkedList
MidTheoryTrick

Difference between Arrays.asList, List.of, and Collections.unmodifiableList.

All three produce a List, but they differ in mutability, null handling, structural sharing, and backing storage. Picking the wrong one causes silent bugs (mutations leaking through wrappers) or runtime explosions (UnsupportedOperationException, NullPointerException).

Side-by-Side

AspectArrays.asList(a,b,c)List.of(a,b,c)Collections.unmodifiableList(list)
IntroducedJava 1.2Java 9Java 1.2
Returned classjava.util.Arrays$ArrayListImmutableCollections.ListN/List12Collections$UnmodifiableList
add/remove/clearThrow UnsupportedOperationExceptionThrow UnsupportedOperationExceptionThrow UnsupportedOperationException
set(i, e)Works (mutates backing array)Throws UnsupportedOperationExceptionThrows UnsupportedOperationException
Allows null elementsYesNo — NPE on constructionYes (if source allows)
contains(null)Returns true/falseThrows NPEReturns true/false
Backing storageThe supplied array (live view)Internal, structurally sharedThe wrapped list (live view)
Mutating the sourceReflected in the listN/A — no external sourceReflected in the wrapper
SerializationSerializableSerializable (with proxy)Serializable
Optimized for sizeNoYes — List12 for 0–2 elemsNo

Why "Live View" Matters for unmodifiableList

Collections.unmodifiableList does not copy — it wraps. So if you retain a reference to the original list, you can still mutate it, and the "unmodifiable" wrapper will show those changes:

List<String> source = new ArrayList<>(List.of("a", "b"));
List<String> readonly = Collections.unmodifiableList(source);

source.add("c");                       // mutate the original
System.out.println(readonly);          // [a, b, c] — changes leaked through!

readonly.add("d");                     // throws UnsupportedOperationException

For a truly immutable snapshot, copy first:

List<String> snapshot = List.copyOf(source);
// or
List<String> snapshot2 = Collections.unmodifiableList(new ArrayList<>(source));

List.copyOf is the modern idiom — it returns the same immutable list types as List.of, and skips the copy if the input is already one of them.

The Null Behavior

List.of is null-hostile by design: passing null to the factory or calling contains(null) throws NullPointerException. This is intentional — it encourages clean data models. The other two tolerate nulls.

List.of("a", null);            // NullPointerException
List.of("a").contains(null);   // NullPointerException
Arrays.asList("a", null);      // OK
Arrays.asList("a").contains(null); // OK

When to Use Which

  • Building a constant from literals? List.of(...) — fastest, smallest, structurally shared.
  • Adapting an existing array to a List API? Arrays.asList(arr).
  • Defending against mutation by callers? List.copyOf(input) if you must retain — Collections.unmodifiableList(new ArrayList<>(input)) is the longhand.
  • Wrapping internal state for read-only exposure? Collections.unmodifiableList is fine if you guarantee no internal mutation.

Mark your status