In Java, the Stream.toArray() method is a terminal operation that collects the elements of a stream into an array, either an Object[] or, with a generator such as String[]::new, an array of the exact type we need. Primitive streams such as IntStream have their own toArray() that returns an int[].
We convert a stream to an array when an API expects an array, such as a varargs method, ProcessBuilder or a table model, or when a fixed-size, index-based structure fits the next step better than a list.
The following example shows the three forms of toArray() on small streams.
Object[] objects = Stream.of("apple", "banana").toArray(); // [apple, banana]
String[] fruits = Stream.of("apple", "banana").toArray(String[]::new); // [apple, banana]
int[] scores = Stream.of(7, 9, 4).mapToInt(Integer::intValue).toArray(); // [7, 9, 4]
Only the generator form returns a String[], whereas the no-argument form returns an Object[] even for a stream of strings. Further down, we explain why the generator is needed, typed and primitive arrays, the exceptions toArray() can throw, the order of elements and how toArray() compares with toList().
1. toArray() and toArray(IntFunction)
The Stream interface declares two toArray() methods. The no-argument version returns an Object[], because generics are erased at runtime and the stream does not know that its elements are strings. The second version takes an IntFunction<A[]>, called the generator, which receives the number of elements and returns a new array of that length.
| Method | Returns | Typical call |
|---|---|---|
| Object[] toArray() | An Object[] with all elements | stream.toArray() |
| <A> A[] toArray(IntFunction<A[]> generator) | An array created by the generator | stream.toArray(String[]::new) |
![Stream.toArray with a String[]::new generator receiving the element count and returning a typed array](https://howtodoinjava.com/wp-content/uploads/2026/10/java-stream-toarray-generator.png)
The constructor reference String[]::new is a short form of the lambda size -> new String[size], as explained in method references. Both forms do the same thing, and the constructor reference is the one most code uses.
String[] byReference = Stream.of("kiwi", "fig").toArray(String[]::new); // [kiwi, fig]
String[] byLambda = Stream.of("kiwi", "fig").toArray(size -> new String[size]); // [kiwi, fig]
A cast does not turn an Object[] into a String[]. The array object itself has the runtime type Object[], so the cast compiles and fails at runtime.
String[] wrong = (String[]) Stream.of("kiwi").toArray(); // ClassCastException
2. Converting a Stream to a String Array or a Record Array
In most code, toArray() is the last step of a pipeline that filters and maps the elements. For example, we keep the fruits that have more than four letters and convert them to upper case before we create the array.
List<String> basket = List.of("kiwi", "apple", "fig", "banana");
String[] longNames = basket.stream()
.filter(name -> name.length() > 4)
.map(String::toUpperCase)
.toArray(String[]::new); // [APPLE, BANANA]
The generator works the same way for our own types. With a record, the array constructor reference is Product[]::new.
record Product(String name, int price) {}
List<Product> catalog = List.of(new Product("apple", 5), new Product("melon", 12), new Product("fig", 4));
Product[] cheap = catalog.stream().filter(p -> p.price() < 10).toArray(Product[]::new); // [Product[name=apple, price=5], Product[name=fig, price=4]]
Arrays of arrays work too, because String[][]::new is a valid generator. We use it in section 6 to turn text lines into a table.
3. Getting int[], long[] and double[] From a Stream
A Stream<Integer> can produce an Integer[], but not an int[], because int[]::new does not match IntFunction<A[]> with an object type A. To get a primitive array, we switch to a primitive stream with mapToInt(), mapToLong() or mapToDouble(), and call its toArray().
List<Integer> ratings = List.of(4, 5, 3);
Integer[] boxed = ratings.stream().toArray(Integer[]::new); // [4, 5, 3]
int[] primitive = ratings.stream().mapToInt(Integer::intValue).toArray(); // [4, 5, 3]
double[] prices = Stream.of("1.5", "2.25").mapToDouble(Double::parseDouble).toArray(); // [1.5, 2.25]
In the other direction, an IntStream produces an Integer[] after boxed(). Ranges and limited infinite streams convert the same way, which is handy for test data.
int[] firstTen = IntStream.iterate(1, i -> i + 1).limit(10).toArray(); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Integer[] evens = IntStream.rangeClosed(1, 6).filter(i -> i % 2 == 0).boxed().toArray(Integer[]::new); // [2, 4, 6]
For more on primitive conversions, see boxed streams and converting between primitive and object arrays.
4. ArrayStoreException and Wrong Generators
The generator must create an array whose component type can hold every element, otherwise toArray() throws ArrayStoreException at runtime. The compiler does not catch it, because the generator’s type parameter A is not tied to the stream’s element type.
Stream<Object> mixed = Stream.of("apple", 42);
String[] notAllStrings = mixed.toArray(String[]::new); // ArrayStoreException: java.lang.Integer
The safe version keeps only the elements of the target type before collecting. A filter() with String.class::isInstance followed by a map() with String.class::cast does it in two short steps.
List<Object> values = List.of("apple", 42, "fig");
String[] onlyStrings = values.stream().filter(String.class::isInstance).map(String.class::cast).toArray(String[]::new); // [apple, fig]
The generator must also return an array of the requested size, no larger and no smaller. For a stream with a known size, a different length throws IllegalStateException. Writing String[]::new avoids the problem, because it always uses the size it receives.
String[] tooSmall = Stream.of("a", "b", "c").toArray(n -> new String[2]); // IllegalStateException: Begin size 3 is not equal to fixed size 2
5. toArray() vs toList() vs Collection.toArray()
A stream can end in an array or a list, and a list can also become an array without a stream. The choice depends on what the next piece of code expects.
| Code | Result | Notes |
|---|---|---|
| stream.toArray(String[]::new) | String[] | Fixed size, elements can be replaced |
| stream.toList() | Unmodifiable List (Java 16) | Allows null elements, see stream to list |
| list.toArray(String[]::new) | String[] (Java 11) | No stream needed when there is no filtering or mapping |
| list.toArray(new String[0]) | String[] | The older form, still common in existing code |
When the data is already in a collection and needs no pipeline step, Collection.toArray(IntFunction) is the shortest choice, and it uses the same generator syntax. More conversions are listed in converting between a list and an array.
List<String> tags = List.of("new", "sale");
String[] tagArray = tags.toArray(String[]::new); // [new, sale]
The order of the array follows the encounter order of the stream, also for parallel streams. A list or a Stream.of() source gives the same order every time, whereas a HashSet source has no defined order to begin with.
Integer[] doubled = Stream.of(5, 3, 8).parallel().map(i -> i * 2).toArray(Integer[]::new); // [10, 6, 16]
6. Building a String[][] From CSV Lines
A small admin tool shows an uploaded CSV file in a Swing JTable, whose constructor takes an Object[][] for the rows. We read the lines with String.lines(), skip blank lines, split each line into its cells and collect the rows with String[][]::new.
String upload = """
apple,5,red
banana,3,yellow
kiwi,8,green
""";
String[][] rows = upload.lines()
.filter(line -> !line.isBlank())
.map(line -> line.split(","))
.toArray(String[][]::new);
int rowCount = rows.length; // 3
String kiwiColor = rows[2][2]; // "green"
The same pipeline feeds any API that wants a two-dimensional array. For quoted cells that contain commas, a CSV parser is the safer choice, as described in parsing CSV files in Java.
7. Stream to Array FAQs
Typed arrays, primitive arrays, reuse and null entries come up in the related searches for stream-to-array conversion.
7.1. Why does toArray() return Object[] instead of String[]?
Generics are erased at runtime, so the stream cannot create a String[] without help. Pass String[]::new as the generator to get a typed array.
7.2. How do we convert a Stream<Integer> to int[]?
Map it to an IntStream first and call toArray(), as in section 3.
int[] values = Stream.of(1, 2, 3).mapToInt(Integer::intValue).toArray(); // [1, 2, 3]
7.3. Can we call toArray() twice on the same stream?
No. The toArray() method is a terminal operation, so the second call throws IllegalStateException. Keep the array and reuse it, or create a new stream from the source.
7.4. Does toArray() keep null elements?
Yes. A null element becomes a null entry in the array. Filter with Objects::nonNull before toArray() when the array must not contain null.
7.5. How do we convert an array back to a stream?
Use Arrays.stream(array) or Stream.of(array). For an int[], Arrays.stream() returns an IntStream, as covered in converting between stream and array.
8. Conclusion
The toArray() method ends a stream and returns an array. Without arguments, we get an Object[]; with a generator such as String[]::new, we get an array of the exact type, and primitive streams return int[], long[] or double[].
A generator with the wrong component type throws ArrayStoreException, and one that returns the wrong length can throw IllegalStateException, so the constructor reference form is the safe default. The array keeps the encounter order, and for data already in a list, list.toArray(String[]::new) skips the stream.
9. References
- Stream.toArray(IntFunction) Javadoc
- IntStream.toArray() Javadoc
- Collection.toArray(IntFunction) Javadoc
- java.util.stream package, Ordering
Happy Learning !!