skip to content

Explain Collection.toArray() and toArray(T[]). Why does the no-arg version return Object[], and what is the correct way to call the typed overload?

level: middleimportance: should knowfreq 55%

answer

  1. toArray() -> Object[] because of type erasure
  2. never cast toArray() to a typed array (ClassCastException)
  3. toArray(new String[0]) is the idiom (zero-length, JIT-fast)
  4. Java 11: toArray(String[]::new)
  5. oversized array -> null at index size

basics

~20 s

toArray() returns an Object[] because the collection doesn't know its element type at runtime (type erasure). toArray(T[]) returns a properly typed array; the modern idiom is list.toArray(new String[0]), and Java 11 added the cleaner list.toArray(String[]::new).

solid answer

~50 s

Collection has two array-conversion methods. toArray() returns Object[] - it can't return String[] because generics are erased at runtime, so the collection has no record of its element type. toArray(T[] a) takes a typed array argument that supplies the runtime component type via reflection: if a is big enough the elements go into it (and a[size] is set to null), otherwise a new array of the same type is allocated and returned. The historically recommended call was list.toArray(new String[0]) - passing a zero-length array, which is actually as fast as or faster than a presized one on modern JVMs because the JIT optimizes the allocation. Since Java 11 you can write list.toArray(String[]::new), which is the clearest form. A classic bug is casting toArray() to String[] - that throws ClassCastException because the actual runtime type is Object[]. Use the typed overload whenever you need a typed array.

code

java · 15 lines
java
List<String> names = List.of("ann", "bob", "cy");

// 1. No-arg -> Object[]; casting it throws ClassCastException
Object[] objs = names.toArray();
// String[] bad = (String[]) names.toArray();   // RUNTIME ClassCastException

// 2. Typed overload, zero-length array (recommended, pre-Java 11)
String[] a = names.toArray(new String[0]);

// 3. Java 11+ array-constructor reference (clearest)
String[] b = names.toArray(String[]::new);

// 4. Oversized array gets a null terminator at index size
String[] big = names.toArray(new String[5]);
// big -> [ann, bob, cy, null, null]

go deeper

for a junior

Knows toArray() gives Object[] and that toArray(new String[0]) gives a typed String array.

for a middle

Explains type erasure as the reason for Object[], the fill-or-allocate behavior of toArray(T[]), and the ClassCastException pitfall of casting.

for a senior

Discusses the zero-length-vs-presized performance nuance, the Java 11 toArray(IntFunction) overload, and the null-terminator edge case.

for a principal

Connects this to reified arrays vs erased generics in the type system and chooses array vs Stream/Collection boundaries in public APIs to avoid leaking Object[].

## The two methods `Collection<E>` offers two ways to copy its contents into an **array**: ```java Object[] toArray(); <T> T[] toArray(T[] a); ``` ## Why the no-arg version returns Object[] Java generics use **type erasure**: the type parameter `E` exists only at **compile time** and is *erased* at runtime. So at runtime a `List<String>` is just a `List` - the object has **no idea** its elements are `String`s. Because `toArray()` must build an array and the JVM needs a concrete **component type** to do so, the only type it can safely use is the universal supertype **`Object`**. Hence it returns **`Object[]`**. This is why **casting the result fails**: ```java String[] bad = (String[]) list.toArray(); // ClassCastException at runtime ``` The runtime object really *is* an `Object[]`, and you cannot cast an `Object[]` down to `String[]` even if every element is a String - the array's own type is wrong. ## The typed overload toArray(T[] a) The second overload solves this by letting **you** supply an array of the desired type. The collection reads the **runtime component type** from `a` (via reflection: `a.getClass().getComponentType()`) and produces an array of that exact type: - If `a` is **large enough** (`a.length >= size`), the elements are stored into `a`, the slot at index `size` is set to `null` (a sentinel), and `a` itself is returned. - If `a` is **too small**, a **new** array of the same runtime type and the right length is allocated and returned (your `a` is left untouched). So the *return value* (not necessarily your argument) is what you keep. ## How to call it correctly **Idiom 1 - zero-length array (recommended pre-11, still fine):** ```java String[] arr = list.toArray(new String[0]); ``` Passing a **zero-length** array might look wasteful, but benchmarks and the JIT show it is **as fast or faster** than a presized array (`new String[list.size()]`) on modern HotSpot - the empty-array allocation is cheap and the JVM optimizes the path. The presized version also risks a stale `null` if the size changes concurrently. **Idiom 2 - array constructor reference (Java 11+, clearest):** ```java String[] arr = list.toArray(String[]::new); ``` This `toArray(IntFunction<T[]>)` default method takes a generator that builds an array of the needed length. Most readable, no magic zero. **Streams alternative:** ```java String[] arr = list.stream().toArray(String[]::new); ``` ## The null-terminator gotcha If you (mistakenly) pass an **oversized** array, the element after the last copied one is set to `null`. Code that loops to `arr.length` instead of using the collection size can then hit an unexpected `null`. Passing a zero-length array sidesteps this entirely. ## Summary - `toArray()` -> `Object[]` because of erasure; **don't cast it**. - `toArray(T[])` -> typed array; supply the type via a zero-length array or `T[]::new`. - Prefer `toArray(String[]::new)` on Java 11+.

  • Why does (String[]) list.toArray() throw ClassCastException even when every element is a String?
    The runtime object is literally an Object[], not a String[]. Array types are reified, so an Object[] cannot be cast to String[] regardless of its contents. Use toArray(new String[0]) instead.
  • Is new String[0] or new String[list.size()] faster as the toArray argument?
    On modern HotSpot the zero-length array is equal or faster, and it avoids a stale-null risk under concurrent resizing. The zero-length idiom is recommended.

saying these in an interview costs you the question

  • Casting (String[]) collection.toArray()
  • Claiming a presized array is always faster than a zero-length one (benchmarks say otherwise on modern JVMs)
  • Forgetting the null-terminator when passing an oversized array
  • Thinking toArray() can return a typed array if generics are 'smart enough' - erasure prevents it

context