Object Creation Cost & Primitive Streams — Interview Questions
Reviewed by Gurusankar M.
Short Answer
Every object allocation is more than 'a bit of memory' — it's a write that has to be tracked by the garbage collector, and at high enough frequency, allocation rate (not heap size) becomes the dominant driver of GC pause frequency. Stream<Integer> pays this cost per element: each int gets autoboxed into an Integer object as it enters the pipeline. IntStream, LongStream, and DoubleStream exist specifically to avoid it — they carry primitives through the pipeline unboxed, and only box if you explicitly convert back to a boxed Stream<Integer> via .boxed().
On this page
Concept Made Simple
Coffee Chat Question
“What's the real cost of excessive object creation, and how do IntStream/LongStream avoid the autoboxing tax Stream<Integer> pays?”
Remember It Faster
Mind Map Answer
The Integer cache (-128 to 127) hides this cost for small numbers in casual code, which is exactly why the problem is easy to miss until a hot path processes a wide range of values — outside the cache range, every autobox is a fresh heap allocation, one per element, on every pipeline execution.
- Stream<Integer>
- Boxes every element — one allocation per int, outside the -128..127 cache
- IntStream/LongStream/DoubleStream
- Primitives flow through unboxed — no per-element allocation
- Escape analysis
- Can sometimes let the JIT stack-allocate short-lived, non-escaping objects
- .boxed()
- Explicitly opts back into Integer objects when a Stream<Integer> API is needed
Key takeaway: in numeric hot paths, reach for the primitive stream types by default — mapToInt()/mapToLong() instead of a generic Stream<Integer>, saving an allocation per element without changing what the code actually computes.
Learn by Doing
Hands-on Keyboard
List<Integer> values = List.of(1, 2, 3, 200, 300);
// Boxes on the way in, boxes again for the intermediate reduce
int sumBoxed = values.stream().reduce(0, Integer::sum);
// mapToInt unboxes once, then the whole pipeline stays primitive
int sumPrimitive = values.stream().mapToInt(Integer::intValue).sum();
// The Integer cache hides the cost for small numbers, but not for these:
Integer a = 200, b = 200;
System.out.println(a == b); // false — outside the -128..127 cache, real allocationsThink Beyond the Expected
What If?
If escape analysis lets the JIT stack-allocate short-lived objects, does that make this whole concern moot?
Not reliably — escape analysis only applies when the JIT can prove an object never 'escapes' the method (isn't stored, returned, or passed somewhere it could outlive the method), and Stream pipeline internals with captured lambdas and iterator chaining often defeat that proof even when intuitively the object 'should' be short-lived. Using primitive streams removes the boxing allocation outright rather than hoping escape analysis optimizes it away.
Real World
This shows up in profiling as a surprisingly high allocation rate (visible in a GC/allocation profile) coming from a seemingly innocent numeric aggregation pipeline processing millions of records — switching Stream<Integer> to IntStream (or the equivalent for long/double) is a common, low-risk fix that shows up as a measurable drop in young-gen GC frequency.
Real Talk from Guru
When I see mapToInt/mapToLong show up unprompted in someone's Stream code, that tells me they've internalized that boxing isn't free — it's a small detail that correlates strongly with someone who's actually looked at an allocation profile rather than just read about Streams.
Interviewer's Expectation
Keywords they're listening for:
- Connects allocation rate to GC pause frequency, not just heap size
- Explains autoboxing as a per-element allocation in Stream<Integer>
- Names IntStream/LongStream/DoubleStream as the fix
- Knows the Integer cache's range and its limits
Common Mistakes
- Using Stream<Integer> for numeric hot paths without considering IntStream
- Assuming the Integer cache eliminates boxing costs generally
- Not correlating high GC frequency with allocation rate in profiling
Best Practices
- Use mapToInt/mapToLong/mapToDouble for numeric Stream pipelines
- Profile allocation rate, not just heap size, when chasing GC-related latency
- Reserve .boxed() for when a boxed-type API genuinely requires it
Follow-up Questions
- Why does the Integer cache only cover -128 to 127?
- How would you find an unexpectedly high allocation rate in a running application?
- When would you deliberately call .boxed() despite the extra allocation?
Continue Learning with AI
Take this question deeper with your favourite AI assistant. Pick a depth, copy the prompt, or open it directly — AI is your learning companion, not a shortcut.
Plain-language foundations
I'm preparing for a software engineering interview and want to understand this from scratch, as a beginner. Topic: Primitives (Core Java) Interview question: "What's the real cost of excessive object creation, and how do IntStream/LongStream avoid the autoboxing tax Stream<Integer> pays?" Please: 1. Explain the core idea in simple, plain language, using an everyday analogy. 2. Define any technical terms you use. 3. Walk through one small, concrete example. 4. Finish with a single sentence I can easily remember. Keep the tone friendly and assume I'm new to this topic.
Was this answer helpful?
Affiliate links
Tools for Your Developer Workspace
Equipment for a comfortable, productive setup.
As an Amazon Associate I earn from qualifying purchases.
- MonitorView on Amazon : BenQ EW2790U 27-inch Monitor (opens in a new tab)
BenQ EW2790U 27-inch Monitor
- LaptopView on Amazon : Acer Aspire One Laptop (opens in a new tab)
Acer Aspire One Laptop
- Office ChairView on Amazon : Wakefit Gravita Ergonomic Office Chair (opens in a new tab)
Wakefit Gravita Ergonomic Office Chair