In Java, a boxed stream is a Stream<Integer>, Stream<Long> or Stream<Double> created from a primitive stream with boxed(), which wraps each int, long or double in its wrapper object. The IntStream, LongStream and DoubleStream interfaces each have a boxed() method.
We box a primitive stream when the next step needs objects, most often to collect the values into a List, a Set or a Map, or to create an Integer[].
The following example collects the same primitive values into a list, a set and two kinds of arrays.
List<Integer> list = IntStream.of(3, 1, 3).boxed().toList(); // [3, 1, 3]
Set<Integer> set = IntStream.of(3, 1, 3).boxed().collect(Collectors.toSet()); // 1 and 3, in no fixed order
int[] primitives = IntStream.of(3, 1, 3).toArray(); // [3, 1, 3]
Integer[] objects = IntStream.of(3, 1, 3).boxed().toArray(Integer[]::new); // [3, 1, 3]
List<Double> prices = DoubleStream.of(1.5, 2.0).boxed().toList(); // [1.5, 2.0]
The int[] is the one result that needs no boxing, whereas the list, the set and the Integer[] do. We explain why the collectors need objects, how boxed() compares with mapToObj(), every way to collect a primitive stream, unboxing back, the cost of boxing and a real reporting example.
1. Why a Primitive Stream Cannot Use Collectors
The Stream interface has collect(Collector), and every collector in Collectors, such as toList() or groupingBy(), works on objects. A primitive stream has no such method. Its only collect() takes three functions, a supplier, an accumulator and a combiner, so passing a Collector does not compile.
// does not compile: IntStream has no collect(Collector) method
List<Integer> numbers = IntStream.of(1, 2, 3).collect(Collectors.toList());

Calling boxed() turns the IntStream into a Stream<Integer>, and from there every Stream operation and collector is available. The same applies to LongStream with Stream<Long> and to DoubleStream with Stream<Double>.
Stream<Integer> ints = IntStream.range(1, 4).boxed();
Stream<Long> longs = LongStream.of(10L, 20L).boxed();
Stream<Double> doubles = DoubleStream.of(0.5).boxed();
List<Integer> collected = ints.collect(Collectors.toList()); // [1, 2, 3]
2. boxed() vs mapToObj()
The boxed() method is a short form of mapToObj(Integer::valueOf); it wraps each value and changes nothing else. We use mapToObj() when the values must become a different object type in the same step, such as a String or a record.
record Seat(int number) {}
List<Integer> same = IntStream.rangeClosed(1, 3).mapToObj(Integer::valueOf).toList(); // [1, 2, 3]
List<String> labels = IntStream.rangeClosed(1, 3).mapToObj(i -> "Row " + i).toList(); // [Row 1, Row 2, Row 3]
List<Seat> seats = IntStream.rangeClosed(1, 2).mapToObj(Seat::new).toList(); // [Seat[number=1], Seat[number=2]]
Writing boxed().map(i -> “Row ” + i) gives the same labels, but it creates an Integer for every value before the mapping. With mapToObj(), each int goes straight into the lambda, so it is the better choice when the target is not the wrapper type.
3. Collecting a Primitive Stream Into a List, Set or Map
Once the stream is boxed, the collector decides the result type. Since Java 16, Stream.toList() returns an unmodifiable list, which suits most read-only results; when the code adds to the list later, Collectors.toCollection(ArrayList::new) gives a mutable one, as compared in converting a stream to a list.
List<Integer> fixed = IntStream.of(5, 2, 8).boxed().toList();
List<Integer> mutable = IntStream.of(5, 2, 8).boxed().collect(Collectors.toCollection(ArrayList::new));
mutable.add(1);
List<Integer> grown = mutable; // [5, 2, 8, 1]
boolean added = fixed.add(1); // UnsupportedOperationException
A TreeSet keeps the values sorted and drops duplicates, and a Map comes from groupingBy() or toMap(). For example, we group numbers by whether they are even.
Set<Integer> sorted = IntStream.of(9, 3, 9, 1).boxed().collect(Collectors.toCollection(TreeSet::new)); // [1, 3, 9]
Map<Boolean, List<Integer>> evenOdd = IntStream.rangeClosed(1, 6).boxed().collect(Collectors.partitioningBy(i -> i % 2 == 0)); // {false=[1, 3, 5], true=[2, 4, 6]}
Map<Integer, Integer> squares = IntStream.rangeClosed(1, 3).boxed().collect(Collectors.toMap(i -> i, i -> i * i)); // {1=1, 2=4, 3=9}
The three-argument collect() of the primitive stream is the alternative that skips the boxed() call. The values are still boxed inside ArrayList::add, so the result is the same, and the boxed form is easier to read.
ArrayList<Integer> viaSupplier = IntStream.of(4, 6).collect(ArrayList::new, ArrayList::add, ArrayList::addAll); // [4, 6]
For LongStream and DoubleStream, the same calls apply with Long and Double as element types. The mapToObj(Long::valueOf) and mapToObj(Double::valueOf) forms from older code do the same as boxed().
Set<Long> ids = LongStream.of(100L, 200L, 100L).boxed().collect(Collectors.toSet()); // 100 and 200, in no fixed order
4. Collecting a Primitive Stream Into an Array
An array is the one target that does not need boxing. The toArray() method of each primitive stream returns an int[], long[] or double[], and that is the cheapest way to store the values. An Integer[] needs boxed() and the Integer[]::new generator, as explained in stream toArray().
int[] ints = IntStream.rangeClosed(1, 4).toArray(); // [1, 2, 3, 4]
long[] longs = LongStream.of(7L, 9L).toArray(); // [7, 9]
double[] doubles = DoubleStream.of(0.5, 1.5).toArray(); // [0.5, 1.5]
Integer[] wrapped = IntStream.rangeClosed(1, 4).boxed().toArray(Integer[]::new); // [1, 2, 3, 4]
Boxing also converts an existing int[] to a List<Integer>. Note that Arrays.asList(intArray) does not do that; it returns a List<int[]> with one element, as covered in converting a primitive array to a list.
int[] scores = {70, 85, 90};
List<Integer> scoreList = Arrays.stream(scores).boxed().toList(); // [70, 85, 90]
int wrongSize = Arrays.asList(scores).size(); // 1
5. Unboxing a Stream<Integer> Back to IntStream
The reverse step is mapToInt(), which is what we use to get sum(), average() or max() without a comparator. The method reference Integer::intValue unboxes each element.
List<Integer> cart = List.of(5, 12, 3);
int total = cart.stream().mapToInt(Integer::intValue).sum(); // 20
double average = cart.stream().mapToInt(Integer::intValue).average().orElse(0); // 6.666666666666667
Unboxing a null element throws NullPointerException, so a list that can contain null needs a filter before mapToInt(). Primitive streams never contain null, which is one more reason to stay on the primitive side for numeric work.
List<Integer> withGaps = Arrays.asList(5, null, 3);
int unsafe = withGaps.stream().mapToInt(Integer::intValue).sum(); // NullPointerException
int safe = withGaps.stream().filter(Objects::nonNull).mapToInt(Integer::intValue).sum(); // 8
6. The Cost of Boxing
Each boxed value is an object on the heap, so a Stream<Integer> of a million elements can create up to a million Integer objects, which the garbage collector has to clean up later. The Integer.valueOf() method reuses cached objects only for values from -128 to 127, as explained in the wrapper class cache.
The cache also explains a classic bug. Comparing two boxed values with == compares references, which happens to work for small numbers and fails for larger ones. Use equals() or unbox before comparing.
List<Integer> small = IntStream.of(100, 100).boxed().toList();
List<Integer> large = IntStream.of(1000, 1000).boxed().toList();
boolean smallSame = small.get(0) == small.get(1); // true
boolean largeSame = large.get(0) == large.get(1); // false
boolean largeEqual = large.get(0).equals(large.get(1)); // true
The practical rule is to keep filtering, mapping and arithmetic on the primitive stream and box only at the end, right before the collector that needs objects.
7. Building a Rating Histogram
An online shop shows how many reviews gave a product 1 to 5 stars. The ratings arrive as an int[] from the database, and the page needs a sorted Map from stars to count. The values stay primitive for the range check, and boxed() comes right before groupingBy().
int[] ratings = {5, 4, 5, 3, 5, 1, 4, 9};
Map<Integer, Long> histogram = Arrays.stream(ratings)
.filter(r -> r >= 1 && r <= 5)
.boxed()
.collect(Collectors.groupingBy(r -> r, TreeMap::new, Collectors.counting()));
Map<Integer, Long> stars = histogram; // {1=1, 3=1, 4=2, 5=3}
The invalid rating 9 is dropped by the filter before any object is created. The TreeMap::new argument keeps the keys in star order, so the page can print the bars from 1 to 5 without sorting again.
8. Boxed Stream FAQs
Questions about boxed() mostly start with a compile error from collect() or with the choice between boxed() and mapToObj().
8.1. What does boxed() do in Java streams?
The boxed() method converts a primitive stream into a stream of wrapper objects, for example an IntStream into a Stream<Integer>, so that object-based operations and collectors can be used.
8.2. Which call turns an IntStream into a List<Integer>?
Call boxed() and collect with toList(), or with Collectors.toCollection(ArrayList::new) for a mutable list.
List<Integer> digits = IntStream.range(0, 3).boxed().toList(); // [0, 1, 2]
8.3. Should we box before calling sum() or max()?
No. Those methods exist on the primitive stream and work without boxing. Boxing first and using reduce(Integer::sum) gives the same result with extra objects.
8.4. Is there a CharStream or a boxed stream of characters?
No. The JDK has no CharStream, so String.chars() returns an IntStream. To get Character objects, use mapToObj(c -> (char) c).
List<Character> letters = "abc".chars().mapToObj(c -> (char) c).toList(); // [a, b, c]
9. Conclusion
The boxed() method turns an IntStream, LongStream or DoubleStream into a stream of Integer, Long or Double objects, which is the step that makes toList(), toSet(), groupingBy() and typed arrays such as Integer[] available. For a different target type, mapToObj() boxes and transforms in one step.
Primitive arrays need no boxing, because the toArray() method of the primitive stream returns them, and mapToInt() takes us back for numeric work. Keeping the arithmetic primitive and boxing only at the collector avoids extra objects and the == trap with cached Integer values.
10. References
- IntStream.boxed() Javadoc
- IntStream.collect() Javadoc
- Stream.toList() Javadoc
- Integer.valueOf() Javadoc
Happy Learning !!
int[] answer = new int[] { 3, 2 } List<Integer> expected = Arrays.stream(answer).boxed().collect(Collectors.toList()); Object[] objArrayOfIntArrays = new Object[] { new int[] { 1, 2 }, // add 2 new int[] { 1, 2 }, // add 2 new int[] { 3, 2 }, // query freq 2 new int[] { 2, 2 }, // delete 2 new int[] { 3, 2 }, // query freq 2 }My question is, is there a way to turn
objArrayOfIntArraystoList<List<Integer>>?Hi Tim,
Object[] objArrayOfIntArrays1 = new Object[] { new int[] { 1, 2 }, // add 2 new int[] { 1, 2 }, // add 2 new int[] { 3, 2 }, // query freq 2 new int[] { 2, 2 }, // delete 2 new int[] { 3, 2 }, // query freq 2 }; List<List<Integer>> primitiveList = Arrays .stream(objArrayOfIntArrays1) .map(q -> { return Arrays.stream((int[]) q) .boxed() .collect(Collectors.toList()); }).collect(Collectors.toList());Hope it Helps,
Praveen
Object[] objArrayOfIntArrays1 = new Object[] { new int[] { 1, 2 }, // add 2 new int[] { 1, 2 }, // add 2 new int[] { 3, 2 }, // query freq 2 new int[] { 2, 2 }, // delete 2 new int[] { 3, 2 }, // query freq 2 }; List<List<Integer>> primitiveList=Arrays.stream(objArrayOfIntArrays1) .map(q->{return Arrays.stream((int[]) q).boxed().collect(Collectors.toList());}).collect(Collectors.toList());