Java List of: Creating and Initializing Lists
java list of: Learn how to create lists in Java using List.of, Arrays.asList, ArrayList, and streams, with key differences in mutability and performance.
When you search for java list of, you're usually looking for the cleanest way to create a List from a set of elements. Java provides several APIs for this, each with different mutability and performance characteristics. This article compares the common approaches and explains when to use each.
Creating a List from Fixed Elements
The most direct way to create a list from a handful of known elements is to use a static factory method. Java 9 introduced List.of, which returns an immutable list. Before that, developers commonly used Arrays.asList, which returns a fixed-size list backed by an array. Both are concise, but they behave differently.
List<String> immutable = List.of("a", "b", "c"); List<String> fixed = Arrays.asList("a", "b", "c");
The first line creates a list that cannot be modified. Any attempt to add, remove, or replace an element throws UnsupportedOperationException. The second line creates a list that allows element replacement but not structural changes. You can call set on it, but not add or remove.
The Immutable List from List.of()
List.of is a static factory method available since Java 9. It returns an immutable list, which means the list's size and element order are fixed. This is useful when you want to pass a constant collection to a method without worrying about accidental modification. It also allows the JVM to optimize memory usage because the list can be stored compactly.
List<Integer> numbers = List.of(1, 2, 3);
List.of rejects null elements. If you try to include a null, it throws NullPointerException. This is a deliberate design choice to prevent nulls from appearing in immutable collections. If you need to allow nulls, you must use a different approach.
Another important detail is that List.of does not guarantee the iteration order for lists with more than a certain number of elements? Actually, it does guarantee insertion order because it's a list. The order is the order in which elements are provided. However, the implementation may vary, but the contract is that the iteration order is the same as the order of the arguments.
The Fixed-Size List from Arrays.asList
Arrays.asList has been around since Java 1.2. It takes an array or a variable number of arguments and returns a list backed by the original array. This means changes to the list are reflected in the array, and vice versa. The list is fixed in size, so you cannot add or remove elements, but you can replace existing elements with set.
String[] array = {"x", "y", "z"}; List<String> list = Arrays.asList(array); list.set(0, "changed"); System.out.println(array[0]); // prints "changed"
This behavior is useful when you need to view an array as a list without copying data. However, it can be a trap if you forget that structural modifications are not allowed. Calling add or remove throws UnsupportedOperationException.
Building a Mutable List with ArrayList
If you need a fully mutable list, the standard approach is to create an ArrayList and populate it. The most common idiom is to wrap Arrays.asList in a new ArrayList:
List<String> mutable = new ArrayList<>(Arrays.asList("a", "b", "c"));
This copies the elements into a new ArrayList, giving you a list that supports add, remove, and set without affecting the original array. The downside is an extra copy, which is negligible for small lists but worth considering for large ones.
Alternatively, you can use the add method repeatedly, but the constructor approach is more concise. For Java 9+, you can also use new ArrayList<>(List.of(...)) to get a mutable copy of an immutable list.
Converting Streams to Lists
When you have a stream of elements, you can collect them into a list. The classic way is Collectors.toList(), but Java 16 introduced Stream.toList(), which returns an unmodifiable list. The distinction matters for your code's intent.
List<String> collected = stream.collect(Collectors.toList()); // mutable List<String> unmodifiable = stream.toList(); // immutable
Collectors.toList() does not guarantee the mutability of the returned list; it may return an ArrayList or something else. In practice, it usually returns a mutable list, but the contract doesn't promise it. If you need a mutable list, you can explicitly collect into a specific collection:
List<String> mutable = stream.collect(Collectors.toCollection(ArrayList::new));
Stream.toList() returns a truly immutable list, similar to List.of. It also disallows null elements, throwing NullPointerException if a null appears in the stream.
Choosing the Right List Creation Method
The following table summarizes the key differences:
| Method | Mutability | Null elements | Backing data | Since |
|---|---|---|---|---|
List.of | Immutable | Not allowed | None (implementation-specific) | Java 9 |
Arrays.asList | Fixed-size, but set allowed | Allowed | Original array | Java 1.2 |
new ArrayList<>(Arrays.asList(...)) | Fully mutable | Allowed | New array copy | Java 1.2 |
stream.collect(Collectors.toList()) | Usually mutable, not guaranteed | Allowed | New collection | Java 8 |
stream.toList() | Immutable | Not allowed | New collection | Java 16 |
Use List.of when you need a constant list that will never change and you're on Java 9 or later. Use Arrays.asList when you need to view an array as a list and you're careful not to change its size. Use new ArrayList<>(Arrays.asList(...)) when you need a fully mutable list and you're not concerned about the copy overhead. Use Stream.toList() when you have a stream and want an immutable result. Use Collectors.toList() when you need a mutable list from a stream and you're on Java 8 or later, but be aware that the mutability isn't guaranteed by the API.
Common Pitfalls and Runtime Behavior
A frequent mistake is treating Arrays.asList as a fully mutable list. For example, this code fails at runtime:
List<String> list = Arrays.asList("a", "b"); list.add("c"); // throws UnsupportedOperationException
The error is not caught at compile time because Arrays.asList returns a List that implements add, but the implementation throws an exception. The same happens with List.of for any modification. Always check the documentation or the method name to understand the mutability contract.
Another pitfall is assuming that List.of preserves order for large lists. It does, but the implementation may use different internal structures. The order is always the insertion order, so you can rely on it.
Null handling is another difference. List.of and Stream.toList() reject nulls, while Arrays.asList and ArrayList allow them. If you're migrating code from Arrays.asList to List.of, you may encounter NullPointerException if any element is null. This is often a good thing because it forces you to handle nulls explicitly.
Performance and Memory Characteristics
The performance of list creation depends on the method. List.of can be very efficient because it may return a specialized implementation that stores elements in a compact form, sometimes without an array at all for small sizes. Arrays.asList is also cheap because it just wraps the array reference. The ArrayList constructor copies the elements, which adds O(n) time and memory. Stream collection involves more overhead due to the stream pipeline, but for moderate sizes it's usually negligible.
For read-heavy code that passes lists around without modification, List.of reduces memory footprint and improves cache locality. For mutable lists, ArrayList is the standard choice. There's no need to micro-optimize unless profiling shows a bottleneck. The bigger concern is correctness: choosing an immutable list when you need to modify it later leads to runtime exceptions.
When you need to convert a large array to a mutable list, consider whether you can avoid the copy by using Arrays.asList and then creating a new list only if you must modify it. If the list is only read, Arrays.asList is sufficient and avoids the copy.
Finally, be aware that Stream.toList() and List.of are not the same as Collections.unmodifiableList. The latter wraps an existing list and delegates reads but throws on writes. The former are truly immutable in the sense that the underlying data cannot change. This distinction matters if you're storing the list in a field and want to guarantee that no code can mutate it.
For most applications, the choice between these methods comes down to mutability requirements and Java version. If you're on Java 9 or later, prefer List.of for fixed sets of elements and Stream.toList() for stream results. If you need a mutable list, use new ArrayList<>(...) and accept the copy cost. The key is to be explicit about your intent so that the next developer reading your code understands whether the list is meant to change.