Java ArrayList to Array: Safe Type Conversion
java arraylist to array: Convert a Java ArrayList to an array safely. Learn the toArray() overloads, type erasure pitfalls, and the zero-length array idiom.
java arraylist to array requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
Converting a java.util.ArrayList to an array is a routine operation in Java, but the two overloads of toArray() behave differently enough that choosing the wrong one produces a ClassCastException at runtime. The conversion becomes necessary when you need to pass data to a legacy API that accepts only arrays, when you want indexed access in a tight loop, or when you need a snapshot of the collection that callers cannot mutate through the list interface.
The Basic toArray() Call and Its Object[] Return Type
The no-argument toArray() method returns an Object[]:
List<String> names = new ArrayList<>(); names.add("Ada"); names.add("Grace"); Object[] objects = names.toArray();
The returned array contains the same elements in the same order as the list, but its static type is Object[]. You cannot assign it directly to a String[] variable, and casting it fails at runtime because the actual array object was created as Object[], not String[]:
String[] strings = (String[]) names.toArray(); // ClassCastException
The cast fails because array covariance checks the runtime type of the array object, not the types of its elements. The JVM created an Object[] internally, so the cast to String[] is rejected.
Type-Safe Conversion with toArray(T[] a)
The generic overload toArray(T[] a) solves the type problem. You pass an array of the target type, and the method returns an array of that same type:
String[] strings = names.toArray(new String[0]);
When the provided array has enough capacity, the list copies its elements into that array and returns it. When the array is too small, the method allocates a new array of the same runtime type, copies the elements, and returns the new array.
Passing a zero-length array is the standard idiom. The list allocates a new array of the correct type when needed, and the small temporary array is negligible because escape analysis in modern JVMs often removes the allocation entirely.
Pre-Sized Arrays and the Performance Tradeoff
An older idiom passes an array sized to the list length:
String[] strings = names.toArray(new String[names.size()]);
This avoids a second allocation when the array is exactly the right size. The list fills the provided array and returns it directly. For a long time this was considered faster because it eliminated the extra allocation.
Modern JVM implementations, however, optimize the zero-length version well. The reference implementation of ArrayList.toArray(T[]) checks the size and allocates a new array only when the provided array is too small. A zero-length array is almost always too small, so the method allocates one array of the correct size. With a pre-sized array, the method allocates one array of the correct size and then fills it. The difference is one small temporary object, which escape analysis can eliminate.
The practical guidance from the OpenJDK maintainers is to prefer toArray(new T[0]). The zero-length version is simpler, and the pre-sized version can be slower in some JIT scenarios because the size check and array fill add work without a measurable benefit.
What Happens with Primitive Types
ArrayList cannot hold primitives directly. A List<Integer> stores boxed Integer objects, so converting it to an array gives you Integer[], not int[]:
List<Integer> numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); Integer[] boxed = numbers.toArray(new Integer[0]);
If you need a primitive int[], you must unbox manually. The stream API provides a concise way:
int[] primitives = numbers.stream().mapToInt(Integer::intValue).toArray();
This iterates the list, unboxes each element, and produces a primitive array. The same pattern applies to long, double, and the other primitive types.
Common Failure Modes and How to Avoid Them
The most common failure is the ClassCastException described earlier. It happens when developers write (String[]) list.toArray() without understanding that the no-argument overload returns Object[].
A subtler issue arises when the list is empty. Both overloads handle an empty list correctly: toArray() returns an empty Object[], and toArray(new String[0]) returns an empty String[]. No special handling is needed.
Another edge case is a list containing null elements. The conversion preserves null entries in the array, which is consistent with how ArrayList stores them. If downstream code assumes the array contains no null values, it must check explicitly.
Choosing Between Array and List After Conversion
Converting to an array is not always the right move. Arrays are fixed-size, so removing an element requires shifting or copying. Lists provide add, remove, and view operations that arrays lack. Keep the list when the data will change in size.
Convert to an array when you need to:
- Pass data to a legacy API that accepts only arrays
- Use array-based iteration with indexed access in a tight loop
- Return a snapshot of the collection that callers cannot mutate through the list interface
Note that toArray() returns a copy of the elements, not a view. Mutating the returned array does not affect the original list, and vice versa. This copy behavior is usually what callers want, but it means the conversion has an O(n) cost in both time and memory.
Stream-Based Conversion as an Alternative
For simple element copying, toArray(T[]) is the clearest option. When you need to transform elements during conversion, the stream API combines both steps:
String[] upper = names.stream().map(String::toUpperCase).toArray(String[]::new);
The toArray(String[]::new) terminal operation uses an array constructor reference to allocate the result. This is useful when filtering, mapping, or sorting must happen before the array is produced. For a plain copy, the stream version adds overhead without benefit.