Back to Blog
Java

Java ArrayList addAll: Usage, Overloads, and Pitfalls

java arraylist addall: Learn how to use ArrayList addAll in Java: syntax, overloads, performance, and common pitfalls when combining collections.

JavaArrayListCollectionsaddAllJava Collections Framework
Diagram showing two lists merging into one via the ArrayList addAll method.

If you need to merge two lists in Java, the java arraylist addall method is the direct way to append all elements from a collection to an existing ArrayList. It also supports inserting elements at a specific index. This article explains both overloads, how they behave with different collection types, and the performance tradeoffs you should know.

ArrayList addAll Syntax and Overloads

The ArrayList class provides two overloads of addAll:

boolean addAll(Collection<? extends E> c) boolean addAll(int index, Collection<? extends E> c)

The first appends all elements from c to the end of the list. The second inserts them starting at index, shifting any existing elements to the right. Both return true if the list changed as a result of the call.

Here is a minimal example:

List<String> first = new ArrayList<>(List.of("a", "b")); List<String> second = new ArrayList<>(List.of("c", "d")); first.addAll(second); System.out.println(first); // [a, b, c, d]

The second overload:

List<String> list = new ArrayList<>(List.of("a", "b", "e")); list.addAll(1, List.of("c", "d")); System.out.println(list); // [a, c, d, b, e]

The method returns true because the list was modified. If the collection is empty, it returns false.

How addAll Behaves with Different Collection Types

addAll accepts any Collection, not just ArrayList. That includes HashSet, LinkedList, ArrayDeque, or a custom collection. The order of the added elements depends on the iteration order of the source collection. For a HashSet, that order is not guaranteed; for a List or LinkedHashSet, it is typically insertion order.

This flexibility is useful when you need to merge data from multiple sources without converting them to a list first. However, if you rely on a specific order, make sure the source collection provides that guarantee.

Performance Considerations for addAll

The time complexity of addAll is O(n) where n is the number of elements in the source collection, because each element is copied into the target list. The target list may need to grow its internal array to accommodate the new elements. If you know the final size in advance, you can pre-size the target with the ArrayList(int initialCapacity) constructor to avoid multiple resizes.

For example:

List<String> target = new ArrayList<>(first.size() + second.size()); target.addAll(first); target.addAll(second);

This is more efficient than starting with an empty default-capacity list and calling addAll twice, which may trigger two resizes. The default capacity is 10, and the list grows by 50% each time it exceeds capacity, so repeated additions can cause several array copies.

Common Pitfalls with addAll

One common mistake is adding a list to itself:

List<String> list = new ArrayList<>(List.of("a", "b")); list.addAll(list);

This works without an exception, but the behavior is not obvious. The implementation of ArrayList.addAll checks if the source is the same list and copies elements first, so it doesn't loop indefinitely. However, it's a confusing pattern and should be avoided.

Another pitfall is modifying the source collection after calling addAll. The target list holds references to the same objects, not copies. If the source collection is a list of mutable objects, changes to those objects will be visible in both. This is expected but can surprise developers who assume a copy.

Null handling: addAll throws NullPointerException if the collection argument is null. It does not add null elements unless the collection itself contains nulls.

Adding at a Specific Index: Index Overload

The index-based overload addAll(int index, Collection<? extends E> c) inserts elements at the given position. The index must be between 0 and the current size, inclusive. If it's out of bounds, IndexOutOfBoundsException is thrown.

This overload is useful when you need to merge a collection into a specific position without creating a new list. For example:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 5)); numbers.addAll(2, List.of(3, 4)); // numbers becomes [1, 2, 3, 4, 5]

The existing elements from the index onward are shifted to the right. This is O(n) in the worst case because of the shifting.

Alternatives to addAll for Combining Lists

While addAll is the most direct way to combine lists, there are other approaches depending on your needs. If you want a new list without modifying the originals, you can use:

List<String> combined = new ArrayList<>(first); combined.addAll(second);

Or with Java 10+:

List<String> combined = Stream.concat(first.stream(), second.stream()).toList();

The stream approach is more functional but may be less efficient because it creates a new stream pipeline. If you need an immutable result, List.copyOf combined with addAll is not directly possible, but you can build a mutable list and then wrap it.

Maintaining Type Safety with addAll

The signature Collection<? extends E> ensures that you can add a collection of a subtype. For example, if E is Number, you can add a List<Integer> because Integer extends Number. This is a common source of confusion with generics. The wildcard ? extends E allows covariance, which is necessary because Collection<Integer> is not a subtype of Collection<Number>.

If you try to add a collection of a supertype, you'll get a compile-time error. For example, adding a List<Object> to a List<String> is not allowed because Object is not a subtype of String.

java arraylist addall: Practical Usage and Code Examples | RYUSLOG DEV