Definition
- flatMapToInt(Function
) – maps each element to an IntStream and concatenates all resulting IntStreams into one IntStream. - Zero boxing – elements go directly from source to primitive stream without Integer wrapper objects.
- vs flatMap+mapToInt – flatMap returns Stream
(boxing), then mapToInt unboxes. flatMapToInt skips the intermediate boxing. - Analogues – flatMapToLong and flatMapToDouble work the same for long and double primitives.
Each bank account has multiple daily transaction amounts. flatMapToInt opens each account's transaction list and puts all amounts on one primitive conveyor belt with no gift-wrapping (boxing). flatMap+mapToInt would first put wrapped amounts on a gift belt, then unwrap them: extra step, extra wrapping.
Understanding the Concepts
What is flatMapToInt?
import java.util.List;
import java.util.stream.IntStream;
record Account(String id, int[] dailyAmounts) {}
List<Account> accounts = List.of(
new Account("SAV2001", new int[]{5000, 3000, 7000}),
new Account("CUR3001", new int[]{15000, 8000}));
// flatMapToInt: one step, zero boxing
int total = accounts.stream()
.flatMapToInt(a -> IntStream.of(a.dailyAmounts()))
.sum();
// 5000 + 3000 + 7000 + 15000 + 8000 = 38000
How does it compare to flatMap+mapToInt?
// Two steps with boxing:
int total = accounts.stream()
.flatMap(a -> java.util.Arrays.stream(a.dailyAmounts()).boxed()) // Stream<Integer>
.mapToInt(Integer::intValue) // unbox back to int
.sum();
// Same result, but creates Integer objects in between
// One step, no boxing:
int total = accounts.stream()
.flatMapToInt(a -> IntStream.of(a.dailyAmounts()))
.sum();
What is the comparison table?
| Approach | Boxing | Steps | Performance |
|----------|:---:|:---:|-------|
| flatMapToInt | None | 1 | Best |
| flatMap + mapToInt | Integer wrappers | 2 | Worse |
| flatMap + boxed | Integer wrappers | 2 | Worst |
How do flatMapToLong and flatMapToDouble work?
// flatMapToLong: for long primitives
long totalPaise = accounts.stream()
.flatMapToLong(a -> java.util.stream.LongStream.of(a.dailyAmountsPaise()))
.sum();
// flatMapToDouble: for double primitives
double totalRate = loans.stream()
.flatMapToDouble(l -> java.util.stream.DoubleStream.of(l.monthlyRates()))
.average()
.orElse(0.0);
What are real banking use cases?
package com.bank.service;
import java.util.List;
import java.util.stream.IntStream;
public class FlatMapToIntDemo {
record Branch(String name, int[] dailyTransactionCounts) {}
public static void main(String[] args) {
List<Branch> branches = List.of(
new Branch("Mumbai", new int[]{150, 200, 175, 190, 210}),
new Branch("Delhi", new int[]{120, 135, 140, 155, 160}));
// Total transactions across all branches
int totalTxns = branches.stream()
.flatMapToInt(b -> IntStream.of(b.dailyTransactionCounts()))
.sum();
System.out.println("Total transactions: " + totalTxns);
// Max single-day transactions
int maxDay = branches.stream()
.flatMapToInt(b -> IntStream.of(b.dailyTransactionCounts()))
.max()
.orElse(0);
System.out.println("Peak day: " + maxDay);
// Summary statistics
var stats = branches.stream()
.flatMapToInt(b -> IntStream.of(b.dailyTransactionCounts()))
.summaryStatistics();
System.out.println("Stats: " + stats);
}
}
Total transactions: 1635
Peak day: 210
Stats: IntSummaryStatistics{count=10, sum=1635, min=120, average=163.5, max=210}
What are common mistakes?
// WRONG: unnecessary boxing with flatMap + boxed
int total = accounts.stream()
.flatMap(a -> java.util.Arrays.stream(a.dailyAmounts()).boxed()) // boxing!
.mapToInt(i -> i) // unboxing!
.sum();
// CORRECT: flatMapToInt avoids boxing entirely
int total = accounts.stream()
.flatMapToInt(a -> IntStream.of(a.dailyAmounts()))
.sum();
When should you prefer flatMap over flatMapToInt?
Use flatMap when the intermediate stream needs non-primitive operations like filter with object Predicates or collect into a List.
// flatMap needed: collecting to List<Integer>
List<Integer> allAmounts = accounts.stream()
.flatMap(a -> java.util.Arrays.stream(a.dailyAmounts()).boxed())
.toList();
What should a senior engineer know?
- flatMapToInt is the idiomatic choice when flattening to primitives and then aggregating.
- For small datasets the boxing overhead is negligible; for large datasets it matters (GC pressure).
- IntStream.of(int[]) and Arrays.stream(int[]) both produce IntStream from arrays.
- flatMapToInt works with parallel streams the same way as flatMap.
- There is no flatMapToObj. flatMap already returns an object stream.
Visual Overview
flatMapToInt pipeline:
Stream<Account>:
Account(SAV2001, [5000, 3000, 7000])
Account(CUR3001, [15000, 8000])
flatMapToInt(a -> IntStream.of(a.dailyAmounts())):
IntStream: 5000, 3000, 7000, 15000, 8000
.sum() → 38000
Zero Integer wrapper objects created.
Key Takeaway
flatMapToInt, flatMapToLong, and flatMapToDouble map each element to a primitive stream and flatten in one step, avoiding the intermediate boxing that flatMap+mapToInt would create. The pattern is collection.stream().flatMapToInt(item -> IntStream.of(item.getPrimitiveArray())).sum().
Use flatMapToInt when flattening to numbers and then aggregating. It is both clearer and more efficient than the two-step flatMap+boxed+mapToInt approach, especially for large datasets where boxing creates GC pressure.
Remember one sentence: flatMapToInt flattens each element to a primitive IntStream in one step with zero boxing, unlike flatMap+mapToInt which boxes in between.
---