Definition
- Arrays.asList(T...) – returns a fixed-size list that is a view over the original array;
set()writes through to the array, butadd()andremove()throw UnsupportedOperationException because the size is fixed - List.of(T...) – returns a truly immutable list;
set(),add(), andremove()all throw UnsupportedOperationException - Backed view – changes to the original array are visible through the Arrays.asList view, and set() on the view changes the array
- Null handling – Arrays.asList allows null elements; List.of throws NullPointerException
- Primitive array gotcha –
Arrays.asList(new int[]{1,2,3})createsListwith one element (the entire array), notListwith three elements
At National Bank of India, Arrays.asList is like a window into a filing shelf. Through the window, you can rearrange files on the shelf (set), but you cannot add new files or remove existing ones because the shelf has a fixed number of slots. If someone changes a file directly on the shelf, you see the change through the window. List.of is like files sealed in a glass case: no rearranging, no adding, no removing. The primitive array gotcha is like putting an entire box of coins on the shelf: Arrays.asList sees one item (the box), not the individual coins inside.
Understanding the Concepts
What is the difference between Arrays.asList and List.of?
Arrays.asList returns a fixed-size list that is a view over the original array. The list and the array share the same data: changes to one are visible in the other. List.of returns a truly immutable list with no backing array and no way to modify it.
package com.bank.demo;
import java.util.Arrays;
import java.util.List;
public class AsListVsListOfDemo {
public static void main(String[] args) {
// Arrays.asList: fixed-size, backed by array
String[] arr = {"SAV2001", "CUR3001", "FD4001"};
List<String> asList = Arrays.asList(arr);
asList.set(0, "CHANGED"); // set() works
System.out.println("Array after set: " + arr[0]);
System.out.println("List after set: " + asList);
try {
asList.add("NEW-001");
} catch (UnsupportedOperationException e) {
System.out.println("asList.add: UOE (fixed size)");
}
// List.of: truly immutable
List<String> listOf = List.of("SAV2001", "CUR3001", "FD4001");
try {
listOf.set(0, "CHANGED");
} catch (UnsupportedOperationException e) {
System.out.println("List.of.set: UOE (immutable)");
}
}
}
Array after set: CHANGED
List after set: [CHANGED, CUR3001, FD4001]
asList.add: UOE (fixed size)
List.of.set: UOE (immutable)
How does the backed-by-array behaviour work?
Arrays.asList does not copy the array. It creates a wrapper that delegates directly to the array. This means any change to the array is immediately visible in the list, and any set() on the list changes the array.
package com.bank.demo;
import java.util.Arrays;
import java.util.List;
public class BackedByArrayDemo {
public static void main(String[] args) {
String[] accountIds = {"SAV2001", "CUR3001", "FD4001"};
List<String> list = Arrays.asList(accountIds);
// Change array: visible in list
accountIds[1] = "MODIFIED-VIA-ARRAY";
System.out.println("List sees array change: " + list.get(1));
// Change list: visible in array
list.set(2, "MODIFIED-VIA-LIST");
System.out.println("Array sees list change: " + accountIds[2]);
}
}
List sees array change: MODIFIED-VIA-ARRAY
Array sees list change: MODIFIED-VIA-LIST
What is the primitive array gotcha?
Arrays.asList uses varargs: asList(T... a). When you pass a primitive array like int[], the type parameter T is inferred as int[] (the entire array), not int. This creates a List with one element (the array itself), not a List with the array's elements.
package com.bank.demo;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PrimitiveArrayGotchaDemo {
public static void main(String[] args) {
int[] amounts = {10000, 25000, 50000};
// WRONG: creates List<int[]> with ONE element
List<int[]> wrong = Arrays.asList(amounts);
System.out.println("Wrong size: " + wrong.size());
System.out.println("First element type: " + wrong.get(0).getClass().getSimpleName());
// FIX 1: Use Integer[] instead of int[]
Integer[] boxedAmounts = {10000, 25000, 50000};
List<Integer> fix1 = Arrays.asList(boxedAmounts);
System.out.println("Fix1 size: " + fix1.size());
// FIX 2: Use Arrays.stream + boxed
List<Integer> fix2 = Arrays.stream(amounts).boxed().toList();
System.out.println("Fix2 size: " + fix2.size());
System.out.println("Fix2: " + fix2);
// FIX 3: IntStream.of
List<Integer> fix3 = java.util.stream.IntStream.of(amounts)
.boxed()
.collect(Collectors.toList());
System.out.println("Fix3 size: " + fix3.size());
}
}
Wrong size: 1
First element type: int[]
Fix1 size: 3
Fix2 size: 3
Fix2: [10000, 25000, 50000]
Fix3 size: 3
How do you compare all the list creation methods?
| Feature | Arrays.asList | List.of | new ArrayList<>() | Stream.toList() |
|---|---|---|---|---|
| add/remove | UOE | UOE | OK | UOE |
| set() | OK | UOE | OK | UOE |
| Null elements | OK | NPE | OK | Depends |
| Backed by source | Yes (array) | No | No | No |
| Mutable | Partially | No | Yes | No |
| Since | Java 2 | Java 9 | Java 2 | Java 16 |
What are common mistakes?
Mistake: Assuming Arrays.asList returns a fully mutable list
import java.util.*;
// WRONG: asList is fixed-size, not mutable
List<String> list = Arrays.asList("SAV2001", "CUR3001");
list.add("FD4001"); // UnsupportedOperationException!
// CORRECT: wrap in ArrayList for full mutability
List<String> list = new ArrayList<>(Arrays.asList("SAV2001", "CUR3001"));
list.add("FD4001"); // works
Mistake: Passing a primitive array to Arrays.asList
// WRONG: int[] is treated as a single element
int[] ids = {1, 2, 3};
List<?> list = Arrays.asList(ids);
System.out.println(list.size()); // 1, not 3!
// CORRECT: use Integer[] or stream
Integer[] ids = {1, 2, 3};
List<Integer> list = Arrays.asList(ids);
System.out.println(list.size()); // 3
Mistake: Modifying the array and expecting the list to be independent
// WRONG assumption: asList creates an independent copy
String[] arr = {"SAV2001", "CUR3001"};
List<String> list = Arrays.asList(arr);
arr[0] = "HACKED";
System.out.println(list.get(0)); // HACKED! List reflects array changes.
// CORRECT: use List.copyOf or new ArrayList<> for independence
String[] arr = {"SAV2001", "CUR3001"};
List<String> independent = List.of(arr); // Java 9
arr[0] = "HACKED";
System.out.println(independent.get(0)); // SAV2001, unaffected
What are the edge cases?
package com.bank.demo;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class EdgeCasesDemo {
public static void main(String[] args) {
// Edge case 1: Arrays.asList allows null, List.of does not
List<String> asListNull = Arrays.asList("SAV2001", null, "CUR3001");
System.out.println("asList with null: " + asListNull);
try {
List.of("SAV2001", null, "CUR3001");
} catch (NullPointerException e) {
System.out.println("List.of with null: NPE");
}
// Edge case 2: empty list
List<String> emptyAsList = Arrays.asList();
List<String> emptyListOf = List.of();
System.out.println("Empty asList: " + emptyAsList.size());
System.out.println("Empty List.of: " + emptyListOf.size());
// Edge case 3: sorting asList works, sorting List.of does not
List<String> sortable = Arrays.asList("CUR3001", "SAV2001", "ACC-100");
Collections.sort(sortable);
System.out.println("Sorted asList: " + sortable);
}
}
asList with null: [SAV2001, null, CUR3001]
List.of with null: NPE
Empty asList: 0
Empty List.of: 0
Sorted asList: [ACC-100, CUR3001, SAV2001]
When should you use Arrays.asList?
Use Arrays.asList when you need a list view of an existing array and want set() to write through to the array. This is useful for passing an array to an API that accepts List. In new code, prefer List.of for immutable lists.
When should you use List.of?
Use List.of when you need a truly immutable list of known elements. It is the standard choice for constants, configuration lists, test data, and return values that should not be modified.
What should a senior engineer know?
- Arrays.asList returns java.util.Arrays$ArrayList, not java.util.ArrayList. This is a different class with fixed-size semantics.
- List.of(T[]) copies the array. Unlike Arrays.asList, List.of does not retain a reference to the source array.
- Performance. Arrays.asList is O(1) because it wraps the existing array. List.of copies the elements, which is O(n) but creates a more compact structure.
- Collectors.toList() returns a mutable ArrayList. Stream.toList() (Java 16) returns an immutable list. This is a common source of confusion.
What are common interview follow-up questions?
- "What is the difference between Arrays.asList and List.of?" : asList is a fixed-size backed view where set works; List.of is truly immutable.
- "What happens with Arrays.asList(new int[]{})?" : Creates a List
with one element (the array), not a list of integers. - "How do you get a mutable list from an array?" : new ArrayList<>(Arrays.asList(array)).
- "Does sorting work on Arrays.asList?" : Yes, because set() works. Sorting List.of throws UnsupportedOperationException.
Visual Overview
Arrays.asList vs List.of
========================
Arrays.asList(arr):
┌──────────────────┐ ┌──────────────┐
│ Arrays$ArrayList │───▶│ String[] arr │
│ (fixed-size view) │ │ [SAV2001] │
│ set: writes thru │ │ [CUR3001] │
│ add/remove: UOE │ │ [FD4001] │
└──────────────────┘ └──────────────┘
Changes propagate BOTH ways
List.of("SAV2001", "CUR3001", "FD4001"):
┌──────────────────┐
│ ImmutableList │
│ [SAV2001] │ No backing array
│ [CUR3001] │ Truly immutable
│ [FD4001] │ set/add/remove: UOE
└──────────────────┘
Primitive array gotcha:
Arrays.asList(new int[]{1,2,3}):
┌──────────────────────────┐
│ List<int[]> size = 1 │
│ Element 0: int[]{1,2,3} │ Whole array is ONE element!
└──────────────────────────┘
Key Takeaway
Arrays.asList and List.of look similar but have fundamentally different semantics. Arrays.asList creates a fixed-size view backed by the original array: set() writes through to the array, but add() and remove() are blocked. List.of creates a truly immutable list with no connection to any source array: set(), add(), and remove() all throw UnsupportedOperationException.
The most dangerous trap is the primitive array gotcha. Arrays.asList(new int[]{1,2,3}) does not create a list of three integers. It creates a list of one element: the int[] array itself. Because generics do not work with primitives, T is inferred as int[], not int. The fix is to use Integer[] or to stream the primitive array through Arrays.stream().boxed().toList().
In the National Bank of India's data pipeline, Rajesh discovered a bug where transaction amounts stored in an int[] were passed to Arrays.asList and processed as a single-element list. The total calculation was wrong because the entire array was treated as one object. Switching to Arrays.stream(amounts).boxed().toList() fixed the issue and prevented it from happening again.
Remember one sentence: Arrays.asList is a fixed-size backed view where set works but add does not, List.of is truly immutable, and never pass a primitive array to asList because it wraps the entire array as one element.
---