The method Arrays.stream(array) turns an array into a stream, and the terminal operation toArray() collects a stream back into an array, given an array constructor such as String[]::new for object streams. Converting a primitive stream such as IntStream to an array needs no argument, because its toArray() returns an int[].
The round trip lets us run stream operations such as filter(), map(), distinct() and sorted() on array data and still hand an array to code that expects one, such as a varargs method or an older library.
The following example upper-cases an array of host names and builds an int[] from a stream.
String[] hosts = {"db1", "db2", "cache"};
String[] upper = Arrays.stream(hosts).map(String::toUpperCase).toArray(String[]::new); // [DB1, DB2, CACHE]
int[] ports = IntStream.of(8080, 9090).toArray(); // [8080, 9090]
Notice that the object stream needs String[]::new to return a String[], while IntStream knows its array type. We start with the ways to create a stream from an array and the trap with Stream.of(), and continue with toArray() and its generator, primitive and two-dimensional arrays, and a cleanup task from a real config file.
1. Creating a Stream from an Array
The method Arrays.stream() has overloads for object arrays and for int[], long[] and double[]. For an int[], it returns an IntStream of the values, not a Stream of the array. Each overload also takes an index range, which streams a part of the array without copying it first.

String[] fruits = {"apple", "kiwi", "fig", "plum"};
List<String> all = Arrays.stream(fruits).toList(); // [apple, kiwi, fig, plum]
List<String> middle = Arrays.stream(fruits, 1, 3).toList(); // [kiwi, fig]
int[] marks = {40, 75, 90};
int total = Arrays.stream(marks).sum(); // 205
double avg = Arrays.stream(marks).average().orElse(0); // 68.33333333333333
The start index is inclusive and the end index is exclusive, the same rule as String.substring(). An invalid range throws an ArrayIndexOutOfBoundsException when we call the method, before any element is read.
1.1. Stream.of() with a Primitive Array
The method Stream.of() works for object arrays, but for an int[] it creates a stream with one element, the array itself. The varargs parameter T… needs an object type, and int is not one, so the whole int[] becomes the single T.
int[] ids = {3, 1, 2};
long wrongCount = Stream.of(ids).count(); // 1, a Stream<int[]>
long rightCount = IntStream.of(ids).count(); // 3
String[] words = {"a", "b"};
long words2 = Stream.of(words).count(); // 2, same as Arrays.stream(words)
The four calls differ only for primitive arrays, so Arrays.stream() is the one to use everywhere, because it returns the right stream type for both kinds of arrays and also supports ranges.
| Call | Object array | int[] |
|---|---|---|
| Arrays.stream(a) | Stream<T> | IntStream |
| Arrays.stream(a, from, to) | Stream<T> of a range | IntStream of a range |
| Stream.of(a) | Stream<T> | Stream<int[]> with one element |
| IntStream.of(a) | Does not compile | IntStream |
2. Stream to Array with toArray()
The method Stream.toArray() without arguments returns an Object[], because the stream cannot know the element type at runtime. The overload toArray(IntFunction<A[]> generator) calls our function with the number of elements and fills the array it returns.
List<String> names = List.of("ana", "li", "raj");
Object[] plain = names.stream().toArray(); // [ana, li, raj]
String[] typed = names.stream().toArray(String[]::new); // [ana, li, raj]
String[] lambda = names.stream().toArray(size -> new String[size]); // [ana, li, raj]
The constructor reference String[]::new is a short form of the lambda size -> new String[size]. The generator must return an array that can store every element, otherwise the call fails with an ArrayStoreException as soon as an element does not fit the array type.
Stream<Object> mixed = Stream.of("ok", 42);
String[] fails = mixed.toArray(String[]::new); // ArrayStoreException: java.lang.Integer
The Stream toArray() tutorial has more examples of both overloads.
3. Primitive Streams and Their Arrays
Each of the primitive streams IntStream, LongStream and DoubleStream has a toArray() method that returns its primitive array. To move between a Stream<Integer> and an IntStream, we call mapToInt() in one direction and boxed() in the other.
Stream<Integer> boxedScores = Stream.of(70, 85, 90);
int[] raw = boxedScores.mapToInt(Integer::intValue).toArray(); // [70, 85, 90]
Integer[] objects = IntStream.of(raw).boxed().toArray(Integer[]::new); // [70, 85, 90]
long[] big = LongStream.rangeClosed(1, 3).map(n -> n * 1_000_000_000L).toArray(); // [1000000000, 2000000000, 3000000000]
double[] prices = DoubleStream.of(9.5, 4.25).toArray(); // [9.5, 4.25]
For char, byte, short and float arrays, Java has no stream type. We widen the values into an IntStream or a DoubleStream, for example with String.chars() for characters or an index stream over the array.
char[] letters = {'j', 'a', 'v', 'a'};
int[] codes = new String(letters).chars().toArray(); // [106, 97, 118, 97]
long distinct = new String(letters).chars().distinct().count(); // 3
byte[] data = {10, -1, 7};
int[] unsigned = IntStream.range(0, data.length).map(i -> data[i] & 0xFF).toArray(); // [10, 255, 7]
4. Two-Dimensional Arrays and Streams
A stream over an int[][] has the rows as its elements, so each element is an int[]. The method flatMapToInt() turns the rows into one IntStream of all values, and toArray(int[][]::new) collects rows back into a two-dimensional array.
int[][] seats = {{1, 0, 1}, {0, 0, 1}};
int[] flat = Arrays.stream(seats).flatMapToInt(Arrays::stream).toArray(); // [1, 0, 1, 0, 0, 1]
int booked = Arrays.stream(seats).flatMapToInt(Arrays::stream).sum(); // 3
int[][] copy = Arrays.stream(seats).map(int[]::clone).toArray(int[][]::new); // [[1, 0, 1], [0, 0, 1]]
copy[0][0] = 9;
int original = seats[0][0]; // 1
Mapping each row through int[]::clone creates new rows, so the copy is independent of the original. A plain seats.clone() would copy only the outer array and share the rows, as the article on shallow and deep array copies explains.
5. Cleaning a Comma-Separated Config Value
Say a service reads its allowed origins from one configuration property, written by hand by different people. The value contains extra spaces, an empty entry and the same host in two spellings, but the HTTP client expects a clean String[].
String property = " api.shop.com, cdn.shop.com,, API.shop.com ";
String[] origins = Arrays.stream(property.split(","))
.map(String::strip)
.filter(s -> !s.isEmpty())
.map(s -> s.toLowerCase(Locale.ROOT))
.distinct()
.toArray(String[]::new);
String result = Arrays.toString(origins); // "[api.shop.com, cdn.shop.com]"
The array that split() returns goes into a stream, each step fixes one problem, and toArray() produces the array the client wants. More ways to break a string into parts are in converting a String to a String array.
6. Stream and Array FAQs
Ordering, reuse and the newer toList() method cause most of the confusion around stream and array conversions.
6.1. Should we use toArray() or toList()?
We use toArray() when the next piece of code needs an array, and toList() in all other cases. The method toList(), added in Java 16, returns an unmodifiable List, which works with the rest of the Collections API without another conversion.
6.2. Does toArray() keep the order of a parallel stream?
Yes, when the source is ordered, such as an array or a List. An ordered stream keeps its encounter order through operations like map() and filter(), so toArray() returns the elements in source order even when threads process them in parallel.
int[] doubled = IntStream.rangeClosed(1, 8).parallel().map(n -> n * 2).toArray(); // [2, 4, 6, 8, 10, 12, 14, 16]
6.3. Can we call toArray() twice on the same stream?
No. A stream is consumed by its terminal operation, so the second call throws IllegalStateException, as described in stream has already been operated upon or closed. We create a new stream from the array for every pipeline.
6.4. How do we create an array of a generic type T from a stream?
We let the caller pass the generator, because Java cannot create new T[size]. The JDK uses the same pattern in Collection.toArray(IntFunction).
static <T> T[] toSortedArray(Collection<T> items, Comparator<? super T> order, IntFunction<T[]> generator) {
return items.stream().sorted(order).toArray(generator);
}
String[] sortedNames = toSortedArray(List.of("raj", "ana", "li"), Comparator.naturalOrder(), String[]::new); // [ana, li, raj]
7. Conclusion
The method Arrays.stream() creates the right stream for any array, including IntStream for an int[] and a sub-range when we pass two indexes. The method Stream.of() is safe only for object arrays.
In the other direction, object streams need a generator such as String[]::new to return a typed array, and primitive streams return int[], long[] or double[] without a generator. With mapToInt(), boxed() and flatMapToInt(), we can move between boxed, primitive and two-dimensional arrays in one pipeline.
8. References
- Arrays.stream() (Java SE 25 API)
- Stream.toArray(IntFunction) (Java SE 25 API)
- IntStream (Java SE 25 API)
- java.util.stream package, Ordering (Java SE 25 API)
Happy Learning !!