Back to Blog
Java

Java ArrayList Initialization: Syntax and Pitfalls

java arraylist initialization: Learn the correct ways to initialize an ArrayList in Java, from constructors to List.of(), and avoid common initialization mistakes.

javaarraylistjava-collectionslist-initializationarrays-aslistlist-of
Illustration of Java ArrayList initialization showing multiple source containers feeding into a single ArrayList container, with a warning about mutability.

Java ArrayList initialization is a common source of confusion because several APIs look similar but produce lists with different mutability and type behavior. The choice between the no-argument constructor, Arrays.asList(), and List.of() changes what you can do with the list after it is created.

The most direct initialization is the no-argument constructor:

ArrayList<String> names = new ArrayList<>();

This creates an empty list backed by an internal array with a default capacity of ten. The list grows automatically as you add elements, but each growth step copies the backing array to a new, larger one. For most code, this constructor is the right starting point because the final size is unknown and the growth cost is amortized across add calls.

Constructor with Initial Capacity

If you know the approximate number of elements the list will hold, pass that value to the constructor:

ArrayList<String> names = new ArrayList<>(100);

This allocates the backing array immediately, avoiding repeated resizing during a large batch of add calls. The capacity is not a limit; the list still grows beyond it when needed. Choosing an initial capacity matters when you insert hundreds of thousands of elements, because each resize copies the entire backing array. A rough estimate is enough; you do not need an exact count.

Initializing from Another Collection

The ArrayList(Collection<? extends E>) constructor copies the elements of any existing collection into a new list:

List<String> source = List.of("a", "b", "c"); ArrayList<String> copy = new ArrayList<>(source);

The new list is independent of the source: modifying copy does not affect source. This constructor is useful when you need a mutable list derived from a collection that is immutable or read-only, such as the result of List.of().

Arrays.asList() and Its Limitations

Arrays.asList() returns a fixed-size list backed by the array you pass:

List<String> list = Arrays.asList("a", "b", "c");

This is not an ArrayList. It is a private implementation with a fixed size. You can call set to replace elements, but you cannot call add or remove; those methods throw UnsupportedOperationException. If you need a true ArrayList, wrap the result:

ArrayList<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

This pattern is common but copies the array into a new backing array. For small lists the overhead is negligible.

List.of() for Immutable Lists

Java 9 introduced List.of(), which returns an immutable list:

List<String> list = List.of("a", "b", "c");

This is not an ArrayList and cannot be modified. Calling add or set throws UnsupportedOperationException. Use List.of() when the data is fixed and you want the runtime to enforce immutability. If you need a mutable list, copy it into an ArrayList as shown in the previous section.

Why Double Brace Initialization Is an Anti-Pattern

Double brace initialization combines an anonymous subclass with an instance initializer:

ArrayList<String> list = new ArrayList<>() {{ add("a"); add("b"); }};

This works, but it creates a new anonymous class each time the code runs. The class holds a reference to the enclosing instance, which can cause memory leaks in long-lived contexts such as static collections. The generated class also increases bytecode size and complicates debugging. Use Arrays.asList() or List.of() instead.

Performance and Memory Considerations

The main performance concern during initialization is the number of resizes the backing array undergoes. The no-argument constructor starts with capacity ten. Adding one million elements triggers roughly 18 resizes, each copying the entire array. Supplying an initial capacity close to the expected size reduces that copying.

Memory usage is also affected by the backing array. An ArrayList always allocates more space than the number of elements it holds, because it leaves room for future additions. If you build a large list and then remove most elements, the backing array does not shrink automatically. Call trimToSize() to shrink the backing array to the the current size, which is useful when the the list becomes stable.

Common Initialization Mistakes

One frequent mistake is treating Arrays.asList() as a fully mutable list and calling add at runtime, which throws UnsupportedOperationException. Another is using List.of() and assuming it is an ArrayList. If your API requires an ArrayList, copy the the immutable list explicitly.

Another mistake is passing an array directly to the ArrayList constructor. The constructor expects a Collection, not an array, so the following does not compile:

String[] arr = {"a", "b"}; ArrayList<String> list = new ArrayList<>(arr); // compile error

Use Arrays.asList(arr) or List.of(arr) as the intermediate step. The same logic applies when you receive a stream from a framework: collect it into a List first, then copy it into an ArrayList if mutability is required.

java arraylist initialization: Practical Usage and Code Exam | RYUSLOG DEV