Java Boxed Stream: IntStream boxed() to List, Set and Array

Java boxed stream guide with IntStream boxed() and mapToObj(), collecting primitive streams into a List, Set, Map or array, unboxing and boxing cost.

IntStream converted to Stream of Integer with boxed() and back with mapToInt(), with the operations available on each side

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());
IntStream converted to Stream of Integer with boxed() and back with mapToInt(), with the operations available on each side
boxed() moves values to the object side where collectors work; mapToInt() moves them back for numeric operations

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

Happy Learning !!

Source Code on Github

Leave a Comment

  1. 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 objArrayOfIntArrays to List<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());
      

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.