DevNotes
Support
Java 8 & Streams · Chapter 13

Primitive Streams

IntStream, LongStream and DoubleStream — why they exist, boxing overhead, sum/average/max/min, summaryStatistics, boxed() and OptionalInt.

◷ 6 min read§ 11 sections? 6 interview questions{ } 18 code examples

#Introduction

One of the most underrated Java 8 topics. Many developers know Streams, but few understand why primitive streams exist. This is asked frequently in interviews:

If you answer this well, it shows you understand Java internals. By the end of this chapter you'll master IntStream, LongStream, DoubleStream, boxing and unboxing, boxed(), mapToInt() / mapToLong() / mapToDouble(), sum(), average(), max(), min() and summaryStatistics().

#Why Primitive Streams?

Java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

numbers.stream()
       .reduce(0, Integer::sum);

This works. But internally Java is constantly converting back and forth:

Integer
int
Integer
int
Integer
Boxing and unboxing on every step

This has overhead.

What is Boxing?

Converting int → Integer:

Java
int x = 10;
Integer y = x;   // compiler converts it automatically

What is Unboxing?

Converting Integer → int:

Java
Integer x = 10;
int y = x;       // again automatic

Why is this bad?

Imagine 1 crore (10 million) integers. Every Integer ↔ int conversion takes time (and boxed values take extra memory). Primitive streams avoid this.

#Meet IntStream

Instead of Stream<Integer>, use IntStream: primitive values only — no boxing, no unboxing, better performance.

Creating an IntStream

Java
IntStream.of(1, 2, 3, 4, 5)
         .forEach(System.out::println);
Output
1
2
3
4
5

#Numeric Operations

sum()

Instead of numbers.stream().reduce(0, Integer::sum), use:

Java
IntStream.of(1, 2, 3, 4, 5)
         .sum();
Output
15

Cleaner. Faster.

average()

Java
double avg = IntStream.of(2, 4, 6, 8)
        .average()
        .orElse(0);
Output
5.0

Notice: average() returns OptionalDouble, because the stream could be empty.

max() and min()

Java
int max = IntStream.of(5, 8, 2, 10)
        .max()
        .orElseThrow();   // 10 — max() returns OptionalInt

int min = IntStream.of(5, 8, 2, 10)
        .min()
        .orElseThrow();   // 2

count()

Java
long count = IntStream.of(1, 2, 3)
        .count();
Output
3

summaryStatistics()

One of my favourites.

Java
IntSummaryStatistics stats = IntStream.of(2, 5, 8, 10)
        .summaryStatistics();

System.out.println(stats);
Output
IntSummaryStatistics{count=4, sum=25, min=2, average=6.250000, max=10}

Instead of sum(), count(), max(), min() and average() — five traversals — you get everything in one traversal.

#Converting To and From Primitive Streams

mapToInt()

Java
class Employee {
    int age;
}

employees.stream()
         .mapToInt(Employee::getAge)
         .sum();
Stream<Employee>
IntStream

mapToLong()

Java
class File {
    long size;
}

files.stream()
     .mapToLong(File::getSize)
     .sum();

mapToDouble()

Java
class Employee {
    double salary;
}

employees.stream()
         .mapToDouble(Employee::getSalary)
         .average();

boxed()

Suppose you have IntStream.of(1, 2, 3, 4) but need a Stream<Integer>:

Java
IntStream.of(1, 2, 3, 4)
         .boxed();   // now a Stream<Integer>

Very useful — for example, to collect into a List:

Java
List<Integer> list = IntStream.rangeClosed(1, 5)
        .boxed()
        .toList();
Output
[1, 2, 3, 4, 5]

(Stream.toList() is Java 16+; on Java 8 use .collect(Collectors.toList()).)

The Conversion Cycle

Stream<Employee>
IntStream
Stream<Integer>
Very common in interviews

#LongStream and DoubleStream

Same idea.

Java
LongStream.of(100L, 200L, 300L)
          .sum();

DoubleStream.of(12.5, 8.5, 15.2)
            .average();

#Real Spring Boot Examples

Java
// Total salary
double total = employees.stream()
        .mapToDouble(Employee::getSalary)
        .sum();

// Average age
double avg = employees.stream()
        .mapToInt(Employee::getAge)
        .average()
        .orElse(0);

// Largest file
long max = files.stream()
        .mapToLong(File::getSize)
        .max()
        .orElse(0);

// Invoice total
double invoiceTotal = invoices.stream()
        .mapToDouble(Invoice::getAmount)
        .sum();

#Interview Questions

Q1Difference between Stream<Integer> and IntStream?

IntStream stores primitive int values and avoids boxing/unboxing.

Q2Why use mapToInt()?

Because it returns an IntStream, allowing efficient numeric operations like sum(), average(), max() and min().

Q3What does boxed() do?

Converts IntStream → Stream<Integer>.

Q4Difference between sum() and reduce(Integer::sum)?

Use sum() whenever possible. It's simpler and optimized.

Q5What does summaryStatistics() return?

Everything — count, sum, average, min and max — in one traversal.

#Practice Problems

Using IntStream.of(2, 5, 8, 10, 15):

  1. Sum
  2. Average
  3. Maximum
  4. Minimum
  5. Count
  6. Summary statistics
  7. Square every number
  8. Cube every number
  9. Filter even numbers
  10. Convert to List<Integer>
Java
class Employee {
    int id;
    int age;
    double salary;
}
  1. Total salary
  2. Average salary
  3. Highest salary
  4. Lowest salary
  5. Total age
  6. Average age
  7. Highest age
  8. Lowest age
  9. Salary statistics
  10. Age statistics

5+ Years Interview Challenge

Return a Map<String, DoubleSummaryStatistics> where key = department and value = salary statistics (count, sum, min, average, max).

Java
Map<String, DoubleSummaryStatistics> result = employees.stream()
        .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.summarizingDouble(Employee::getSalary)
        ));

An excellent example of combining groupingBy(), primitive collectors and summary statistics. It's concise, efficient, and commonly seen in reporting code.

#Senior Java Tips

1. Choose the right stream type

Stream typeElement type
Stream<T>Objects
IntStreamint
LongStreamlong
DoubleStreamdouble

Using primitive streams avoids unnecessary boxing and unboxing.

2. Prefer specialized numeric methods

Instead of stream.mapToInt(...).reduce(0, Integer::sum), write stream.mapToInt(...).sum(). It's more readable and optimized.

3. Remember the Optional variants

Primitive streams return specialized optionals — OptionalInt, OptionalLong and OptionalDouble. These exist to avoid boxing primitive values into Optional<Integer>, Optional<Long> and Optional<Double>.

#Chapter Summary

  • Why primitive streams exist — boxing/unboxing overhead
  • IntStream, LongStream, DoubleStream
  • sum(), average(), max(), min(), count()
  • summaryStatistics() — everything in one pass
  • mapToInt() / mapToLong() / mapToDouble() and boxed()
  • OptionalInt, OptionalLong, OptionalDouble