Back to Stream: flatMap
Q3Question 3 of 7

flatMapToInt/Long/Double: flatten to primitive stream, vs flatMap+mapToInt

Key Concepts
  • flatMapToInt maps each element to an IntStream and flattens all resulting IntStreams into one
  • It is the primitive equivalent of flatMap, avoiding the intermediate Stream<Integer> boxing
  • Preferred when each element produces multiple primitive values that will be aggregated
  • flatMap+mapToInt creates an intermediate object stream with boxing; flatMapToInt avoids this
  • flatMapToLong and flatMapToDouble work identically for long and double primitives
  • Common pattern: accounts.stream().flatMapToInt(a -> IntStream.of(a.getDailyAmounts())).sum()
  • Use flatMapToInt when flattening to numbers and then aggregating for both clarity and performance

Definition

flatMapToInt maps each element to an IntStream and flattens all resulting primitive streams into one, avoiding the boxing that flatMap+mapToInt would create.
  • 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?

JAVA
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?

JAVA
// 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?

JAVA
// 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?

JAVA
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);
    }
}
TEXT
Total transactions: 1635
Peak day: 210
Stats: IntSummaryStatistics{count=10, sum=1635, min=120, average=163.5, max=210}

What are common mistakes?

JAVA
// 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.

JAVA
// 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate