Definition
boxed() bridges from the primitive stream world back to the object stream world.
- IntStream – a stream of raw
intvalues with numeric operations but no object collectors. - boxed() – an intermediate operation that wraps each
intin anIntegerand returns aStream. - Equivalent to
mapToObj(Integer::valueOf), but shorter and clearer. - Purpose – to reach operations that only exist on object streams, such as
collect(toList()),toMap, orsorted(Comparator).
You drop into a primitive stream for fast numeric work and box back only when you need objects.
At National Bank of India, Rajesh does his age arithmetic on a plain tally strip of raw numbers because it is fast (the IntStream). But the reporting system only accepts numbers sealed in labeled envelopes (Integer objects in a List). So after the math is done, Rajesh seals each number into an envelope before handing the batch to the report. boxed() is that sealing step, done once at the end, not on every calculation.
Understanding the Concepts
What does boxed() do?
boxed() is an intermediate operation on a primitive stream that converts each primitive value into its wrapper object. On an IntStream it produces a Stream; on a LongStream a Stream; on a DoubleStream a Stream.
package com.bank.hr;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class BoxingDemo {
public static List<Integer> ages(List<Employee> employees) {
return employees.stream()
.mapToInt(Employee::getAge) // IntStream of raw ints
.boxed() // Stream<Integer>
.collect(Collectors.toList());
}
}
[35, 29, 42]
Why do you need to box at all?
An IntStream has numeric operations but it cannot collect into an object collection, because Collectors.toList() and friends operate on Stream, not on primitive streams. To use those collectors you must first turn the primitives into objects.
// WRONG: IntStream has no collect(Collectors.toList())
// List<Integer> list = employees.stream().mapToInt(Employee::getAge).collect(Collectors.toList());
// CORRECT: box first, then collect
List<Integer> list = employees.stream()
.mapToInt(Employee::getAge)
.boxed()
.collect(Collectors.toList());
How is boxed() related to mapToObj?
boxed() is a convenience method equivalent to mapToObj(Integer::valueOf). Both wrap each int into an Integer, but boxed() is shorter and signals intent.
// These two produce the same Stream<Integer>
IntStream.rangeClosed(1, 3).boxed();
IntStream.rangeClosed(1, 3).mapToObj(Integer::valueOf);
mapToObj is more general: it can map an int to any object type, not only Integer.
// mapToObj mapping ints to a different object type
List<String> labels = IntStream.rangeClosed(1, 3)
.mapToObj(i -> "ACC-" + i)
.collect(Collectors.toList());
// [ACC-1, ACC-2, ACC-3]
How does primitive versus boxed compare?
| Aspect | IntStream | Stream (after boxed()) |
|--------|-------------|--------------------------------------|
| Element type | raw int | Integer object |
| Memory | lower, no object headers | higher, one object per element |
| Numeric ops | sum, average, min, max | not directly, need mapToInt back |
| Object collectors | not available | collect(toList()), toMap, etc. |
| When to use | numeric aggregation | building object collections |
// Numeric math on the primitive stream, then box only to collect
int[] arr = { 35, 29, 42 };
List<Integer> sortedAges = IntStream.of(arr)
.filter(age -> age >= 30) // still primitive, fast
.boxed() // box at the boundary
.sorted()
.collect(Collectors.toList());
// [35, 42]
How do you go back from objects to a primitive stream?
The reverse of boxed() is mapToInt. Starting from a Stream or a List, call mapToInt(Integer::intValue) to unbox into an IntStream.
List<Integer> boxedAges = List.of(35, 29, 42);
int total = boxedAges.stream()
.mapToInt(Integer::intValue) // unbox back to IntStream
.sum();
// 106
What are the common mistakes?
The first mistake is boxing too early and doing numeric math on a boxed stream, which pays the boxing cost on every operation.
// WRONG: box first, then every numeric step unboxes and reboxes
int sum = employees.stream()
.mapToInt(Employee::getAge)
.boxed() // unnecessary here
.mapToInt(Integer::intValue)
.sum();
// CORRECT: stay primitive for the math, box only if you need objects
int sum = employees.stream()
.mapToInt(Employee::getAge)
.sum();
The second mistake is trying to call an object collector directly on an IntStream, which does not compile.
// WRONG: does not compile, IntStream has no collect(Collector)
// employees.stream().mapToInt(Employee::getAge).collect(Collectors.toList());
// CORRECT: box first
employees.stream().mapToInt(Employee::getAge).boxed().collect(Collectors.toList());
What are the edge cases around the Integer cache?
Integer.valueOf, which boxed() uses, caches values from -128 to 127, so boxing those reuses shared objects. Outside that range each box creates a new Integer. This matters if you ever compare boxed values with == instead of equals.
Integer a = IntStream.of(100).boxed().findFirst().get();
Integer b = IntStream.of(100).boxed().findFirst().get();
System.out.println(a == b); // true (100 is cached)
Integer c = IntStream.of(1000).boxed().findFirst().get();
Integer d = IntStream.of(1000).boxed().findFirst().get();
System.out.println(c == d); // false (1000 not cached, new objects)
System.out.println(c.equals(d)); // true (always compare with equals)
When should you use boxed() and when should you avoid it?
Use boxed() at the boundary where you must produce objects: collecting into a List, building a map, sorting with a Comparator, or passing to an API that expects Stream. Avoid it in the middle of numeric pipelines where staying primitive is faster.
// Good use: need a List<Integer> result for an API
List<Integer> ages = employees.stream()
.mapToInt(Employee::getAge)
.boxed()
.collect(Collectors.toList());
// Avoid: boxing then immediately summing wastes the primitive advantage
What should a senior engineer know?
- Boxing cost. Each box allocates an object (unless cached), adding memory pressure and work for the garbage collector; box once at the end, not per element in a hot path.
- Three boxed targets.
IntStream.boxed()givesStream,LongStream.boxed()givesStream,DoubleStream.boxed()givesStream. - Equivalence.
boxed()equalsmapToObj(Integer::valueOf); usemapToObjwhen the target type is not the wrapper. - Collectors shortcut. For a quick list of ints there is also
IntStream.toArray()returningint[]with no boxing at all, which may beatboxed().collect(toList())when you only need an array.
What are common interview follow-up questions?
- "What does
boxed()return on anIntStream?" Answer: aStream. - "Why would you call
boxed()?" Answer: to use object-stream operations likecollect(toList())ortoMapthat primitive streams lack. - "What is
boxed()equivalent to?" Answer:mapToObj(Integer::valueOf). - "How do you go back to a primitive stream?" Answer:
mapToInt(Integer::intValue). - "Why avoid boxing in numeric pipelines?" Answer: it allocates wrapper objects and loses the performance of primitive streams.
Visual Overview
List<Employee>
│ mapToInt(Employee::getAge)
▼
IntStream [ 35, 29, 42 ] (raw ints, fast numeric math)
│
│ boxed() == mapToObj(Integer::valueOf)
▼
Stream<Integer> [ Integer(35), Integer(29), Integer(42) ]
│
│ collect(Collectors.toList())
▼
List<Integer> [ 35, 29, 42 ]
Round trip between the two worlds
Stream<Integer> / List<Integer>
│ mapToInt(Integer::intValue) (unbox)
▼
IntStream (primitive, do the math here)
│ boxed() (box)
▲
Stream<Integer> (object world, collect here)
Rule: box ONCE at the boundary, not inside the numeric loop.
Key Takeaway
When Rajesh does age arithmetic, he works on a plain tally strip of raw numbers because a primitive IntStream is fast and allocates nothing. But the reporting system only accepts numbers sealed in labeled envelopes, meaning Integer objects inside a List. boxed() is the sealing step that wraps each raw int into an Integer, turning an IntStream into a Stream.
The reason you need it is that an IntStream carries numeric operations but not object collectors. You cannot call collect(Collectors.toList()) on an IntStream; those collectors operate on Stream. So when the goal is a List, a map, or anything that needs object-stream operations, you box first and then collect.
Under the hood boxed() is just mapToObj(Integer::valueOf), which is why mapToObj is the tool when you want to map ints to some other type like an account label. The reverse trip exists too: mapToInt(Integer::intValue) unboxes a Stream back into an IntStream when you want to return to fast numeric math.
The cost is real. Each box allocates a wrapper object unless the value sits in the small Integer cache between -128 and 127, so boxing in the middle of a numeric pipeline throws away the very advantage that made you choose a primitive stream. Box once, at the boundary where objects are required.
In our banking work the discipline is simple: stay primitive for the math, box only at the collection boundary, and unbox with mapToInt when you need to compute again.
Remember one sentence: boxed() wraps an IntStream into a Stream so you can use object collectors, and you should call it once at the boundary rather than inside numeric math.
---