Java 8 Summarizing Methods with Examples

  • Last Updated: May 4, 2024
  • By: javahandson
  • Series
img

Java 8 Summarizing Methods with Examples

Java 8 summarizing methods give you count, sum, min, max, and average from a single pass over a stream. This guide covers summarizingInt, summarizingLong, summarizingDouble, the statistics objects they return, and the empty-stream trap that catches almost everyone.

1. Introduction

Java 8 summarizing methods solve a small problem you hit over and over. You have a list of objects, and you need five numbers out of it: how many, the total, the smallest, the biggest, and the average.

The Streams API packs all of it into a single collector. One pass, one object, five getters. Think of a cashier counting a till: they do not count the notes once for the total and again for the largest note. They handle each note once and track everything as they go.

Java gives you three flavours of this collector, one per numeric type. They behave identically, so learning one teaches you all three. The interesting parts are the object they hand back and the surprising thing it does when the stream is empty.

1.1 What This Article Covers

We start with the problem these collectors fix, then work through each variant with a runnable example. Here is the plan:

  • Why one pass beats five, and where these methods live
  • summarizingInt, summarizingLong, and summarizingDouble, signature by signature
  • What sits inside IntSummaryStatistics and its two siblings
  • The empty-stream defaults that quietly break reports
  • Alternatives such as summaryStatistics, summingInt, and averagingInt
  • Per-group statistics using groupingBy and partitioningBy
  • Six common mistakes, a full walkthrough, and interview questions

Basic familiarity with streams and method references helps. If either feels new, our guides on lambda expressions and method references are a good warm-up.

2. Why These Methods Exist

Before the syntax, let us look at the pain these collectors remove. It shows up in almost every reporting task.

2.1 The Multi-Pass Problem

Say you need a quick summary of student marks. Without a summarizing collector, the code sprawls.

long count = studentList.stream().count();
int total  = studentList.stream().mapToInt(Student::getMarks).sum();
int max    = studentList.stream().mapToInt(Student::getMarks).max().orElse(0);
int min    = studentList.stream().mapToInt(Student::getMarks).min().orElse(0);
double avg = studentList.stream().mapToInt(Student::getMarks).average().orElse(0);

Five statements, five passes over the list. Each one repeats the same mapToInt call, and each one needs its own fallback for the empty case.

2.2 One Pass, Five Numbers

A summarizing collector collapses all five lines into one.

IntSummaryStatistics stats = studentList.stream()
        .collect(Collectors.summarizingInt(Student::getMarks));

System.out.println(stats.getCount());
System.out.println(stats.getSum());
System.out.println(stats.getMin());
System.out.println(stats.getMax());
System.out.println(stats.getAverage());

The collector visits each student once. As it goes, it bumps a counter, adds to a running total, and keeps the smallest and largest values seen so far. The average falls out of the total and the count at the end.

2.3 Where They Live

All three methods sit in java.util.stream.Collectors, next to toList, groupingBy, and the rest of the factory methods.

  • Each one is static, so you call it as Collectors.summarizingInt(…)
  • Every variant returns a Collector, which you hand to stream.collect(…)
  • Java 8 shipped all three together, and the signatures have not changed since
  • None of them ever returns null, even for an empty stream

Our introduction to the Collectors class maps out the wider family if you want the full picture first.

3. The Three Variants at a Glance

Three methods, three numeric types, one shared design. Learn the shape once and you have learned all of them.

3.1 The Comparison Table

Method Mapper Returns sum / min / max
summarizingInt ToIntFunction IntSummaryStatistics long / int / int
summarizingLong ToLongFunction LongSummaryStatistics long / long / long
summarizingDouble ToDoubleFunction DoubleSummaryStatistics double / double / double

That last column lists the types of getSum(), getMin(), and getMax(), in that order. One row there deserves a second look. The int variant returns a long from getSum(), not an int, and section 4.5 explains why that choice matters.

3.2 Picking the Right One

The rule is simple: match the variant to the type your getter already returns.

  • Your field is an int, such as marks or units, so reach for summarizingInt
  • A long field, like an id or an epoch millisecond value, calls for summarizingLong
  • Anything with a decimal point, such as price or a rating, wants summarizingDouble
  • Mixing types works, but only in one direction, since Java widens int to long and long to double

Never narrow the other way with a cast. Squeezing a long id into summarizingInt silently mangles large values, and section 11.2 shows exactly how.

4. The summarizingInt Method

Start here. Once this variant clicks, the other two take about a minute each.

4.1 The Syntax

public static <T> Collector<T, ?, IntSummaryStatistics>
        summarizingInt(ToIntFunction<? super T> mapper)

4.2 Reading the Signature

Generic signatures look worse than they are. Break this one into four pieces and it turns friendly.

Start with public static. The method belongs to the class rather than an instance, so you always write Collectors.summarizingInt(…).

Then comes <T>, the type your stream carries. Stream a list of Student objects and T becomes Student. Generics keep the method usable with any class you write.

Next is the return type, Collector<T, ?, IntSummaryStatistics>. A Collector declares three types:

  • The first is what goes in, which matches your stream elements
  • A wildcard sits in the middle, hiding the temporary accumulator
  • Last comes the finished result, an IntSummaryStatistics object

Finally, ToIntFunction<? super T> mapper is the extractor. It is a functional interface that takes one object and returns a plain int. Write it as a method reference such as Student::getMarks, or as a lambda such as s -> s.getMarks().

4.3 The Student Class

package com.javahandson.collectors.summarizing;

public class Student {

    int rollNumber;
    String name;
    int marks;

    public Student(int rollNumber, String name, int marks) {
        this.rollNumber = rollNumber;
        this.name = name;
        this.marks = marks;
    }

    public String getName() {
        return name;
    }

    public int getMarks() {
        return marks;
    }
}

4.4 The Example

Let us pull a full summary of the class marks in one shot.

package com.javahandson.collectors.summarizing;

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

public class SummarizingIntDemo {
    public static void main(String[] args) {

        List<Student> studentList = Arrays.asList(
                new Student(101, "Suraj", 450),
                new Student(102, "Iqbal", 470),
                new Student(103, "Amar", 430),
                new Student(104, "Amit", 400),
                new Student(105, "Suchit", 380),
                new Student(106, "Kartik", 490));

        IntSummaryStatistics marksStats = studentList.stream()
                .collect(Collectors.summarizingInt(Student::getMarks));

        System.out.println("Number of students: " + marksStats.getCount());
        System.out.println("Total marks: " + marksStats.getSum());
        System.out.println("Maximum marks: " + marksStats.getMax());
        System.out.println("Minimum marks: " + marksStats.getMin());
        System.out.println("Average marks: " + marksStats.getAverage());
    }
}
// Output:
// Number of students: 6
// Total marks: 2620
// Maximum marks: 490
// Minimum marks: 380
// Average marks: 436.6666666666667

Six students, one pass, five answers. Check the sum if you like. The six marks add up to 2620, and six goes into that 436.67 times.

You can also print the object directly. Its toString packs everything onto one line, which is handy while debugging.

System.out.println(marksStats);
// Output:
// IntSummaryStatistics{count=6, sum=2620, min=380, average=436.666667, max=490}

4.5 Why getSum Returns a long

Here is a design detail worth knowing. You feed int values in, yet getSum() hands back a long.

The reason is overflow. An int tops out just above two billion. Add up ten thousand salaries or a million page-view counts, and an int total wraps around into a negative number without any warning.

A long holds roughly nine quintillion. To overflow it with int inputs you would need billions of elements, which no realistic stream reaches. So the sum stays correct no matter how long your list grows.

long total = marksStats.getSum();   // long, safe from overflow
int  worst = marksStats.getMin();   // int, matches the input type
int  best  = marksStats.getMax();   // int
long howMany = marksStats.getCount();
double mean  = marksStats.getAverage();

Keep this in mind when you assign the result. Writing int total = marksStats.getSum(); fails to compile, and that error is the compiler protecting you.

5. The summarizingLong Method

Same idea, wider numbers. Use this variant when your extractor returns a long.

5.1 The Syntax

public static <T> Collector<T, ?, LongSummaryStatistics>
        summarizingLong(ToLongFunction<? super T> mapper)

5.2 What Actually Changes

Put the two signatures side by side and only two words move.

  • The mapper interface becomes ToLongFunction instead of ToIntFunction
  • Your result is a LongSummaryStatistics object rather than an int one
  • Both getMin() and getMax() now hand back a long
  • Everything else, including getCount() and getAverage(), stays identical

Typical long fields include database ids, epoch timestamps, file sizes in bytes, and view counters. Anything that might outgrow two billion belongs here.

5.3 The Product Class

package com.javahandson.collectors.summarizing;

public class Product {

    long productId;
    String productName;
    double price;

    public Product(long productId, String productName, double price) {
        this.productId = productId;
        this.productName = productName;
        this.price = price;
    }

    public long getProductId() {
        return productId;
    }

    public String getProductName() {
        return productName;
    }

    public double getPrice() {
        return price;
    }
}

5.4 The Example

package com.javahandson.collectors.summarizing;

import java.util.Arrays;
import java.util.List;
import java.util.LongSummaryStatistics;
import java.util.stream.Collectors;

public class SummarizingLongDemo {
    public static void main(String[] args) {

        List<Product> products = Arrays.asList(
                new Product(101L, "Wheat", 155.50),
                new Product(102L, "Rice", 255.00),
                new Product(103L, "Cooking Oil", 525.70),
                new Product(104L, "Cookies", 200.00),
                new Product(105L, "Beans", 150.50));

        LongSummaryStatistics idStats = products.stream()
                .collect(Collectors.summarizingLong(Product::getProductId));

        System.out.println(idStats);
    }
}
// Output:
// LongSummaryStatistics{count=5, sum=515, min=101, average=103.000000, max=105}

5.5 Long Sums Can Still Overflow

Section 4.5 explained how the int variant escapes overflow by widening to long. The long variant has no wider type to escape into.

So a LongSummaryStatistics sum can wrap around. Java does not throw here. It quietly rolls past the maximum into negative territory, and your report shows a total that makes no sense.

6. The summarizingDouble Method

The third variant handles anything with a decimal point. Prices, salaries, ratings, temperatures, and percentages all land here.

6.1 The Syntax

public static <T> Collector<T, ?, DoubleSummaryStatistics>
        summarizingDouble(ToDoubleFunction<? super T> mapper)

By now the shape should feel familiar. Swap in ToDoubleFunction, get back a DoubleSummaryStatistics, and everything else carries over.

6.2 The Example

package com.javahandson.collectors.summarizing;

import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

public class SummarizingDoubleDemo {
    public static void main(String[] args) {

        List<Product> products = Arrays.asList(
                new Product(101L, "Wheat", 155.50),
                new Product(102L, "Rice", 255.00),
                new Product(103L, "Cooking Oil", 525.70),
                new Product(104L, "Cookies", 200.00),
                new Product(105L, "Beans", 150.50));

        DoubleSummaryStatistics priceStats = products.stream()
                .collect(Collectors.summarizingDouble(Product::getPrice));

        System.out.println(priceStats);
        System.out.printf("Cheapest: %.2f%n", priceStats.getMin());
        System.out.printf("Average : %.2f%n", priceStats.getAverage());
    }
}
// Output:
// DoubleSummaryStatistics{count=5, sum=1286.700000, min=150.500000, average=257.340000, max=525.700000}
// Cheapest: 150.50
// Average : 257.34

6.3 Why the Output Shows Six Decimals

That sum=1286.700000 looks odd at first. Nobody stored six decimal places anywhere.

The explanation is plain formatting. Inside toString, the JDK uses the %f pattern, and %f always prints six decimals. The stored value is still an ordinary double.

6.4 Compensated Summation and NaN

Adding many doubles loses precision. Each addition rounds a little, and those roundings pile up across thousands of values.

DoubleSummaryStatistics fights back with compensated summation. It tracks the tiny error left by each addition and folds it back in later. You get a noticeably more accurate total than a plain running += would produce.

One value defeats it completely, though. Feed in a single NaN and the sum, the average, the minimum, and the maximum all turn into NaN. There is no partial recovery.

7. Inside the Statistics Objects

These three classes live in java.util, not in the stream package. They are ordinary objects you can create, feed, and merge by hand.

7.1 The Five Getters

The three variant columns below stand for IntSummaryStatistics, LongSummaryStatistics, and DoubleSummaryStatistics.

Getter Int variant Long variant Double variant
getCount() long long long
getSum() long long double
getMin() int long double
getMax() int long double
getAverage() double double double

Notice that getCount() and getAverage() never change type. A count is always a long, and an average is always a double, whatever went in.

7.2 Building One by Hand With accept

You do not need a stream at all. Create the object, call accept per value, and read the getters.

IntSummaryStatistics stats = new IntSummaryStatistics();

stats.accept(450);
stats.accept(470);
stats.accept(380);

System.out.println(stats);
// Output:
// IntSummaryStatistics{count=3, sum=1300, min=380, average=433.333333, max=470}

7.3 Merging Two With combine

The combine method folds one statistics object into another. Counts add up, sums add up, and the extremes stretch to cover both sides.

IntSummaryStatistics morning = new IntSummaryStatistics();
morning.accept(450);
morning.accept(470);

IntSummaryStatistics evening = new IntSummaryStatistics();
evening.accept(380);
evening.accept(490);

morning.combine(evening);

System.out.println(morning);
// Output:
// IntSummaryStatistics{count=4, sum=1790, min=380, average=447.500000, max=490}

This method is exactly what makes parallel streams work. Each worker thread builds its own statistics object, and the framework merges them pairwise at the end.

7.4 They Are Not Thread-Safe

The javadoc says it plainly: these classes are not thread-safe. Two threads calling accept on one shared object will corrupt the counts.

Yet a parallel stream with summarizingInt is perfectly safe. Why the difference? The framework never shares one object. It hands each thread a private instance and merges them with combine afterwards.

So the rule is easy. Let the collector manage the objects and go parallel freely. Build one by hand and you own the synchronisation. Our article on parallel streams in Java 8 covers when parallel is worth it at all.

8. The Empty Stream Trap

This section is the one to remember. It causes real bugs, and the behaviour surprises almost everybody the first time.

8.1 What min and max Give You

Run a summarizing collector over an empty list. You might expect zeros everywhere, or maybe an exception. Neither happens.

List<Student> nobody = new ArrayList<>();

IntSummaryStatistics stats = nobody.stream()
        .collect(Collectors.summarizingInt(Student::getMarks));

System.out.println(stats);
// Output:
// IntSummaryStatistics{count=0, sum=0, min=2147483647, average=0.000000, max=-2147483648}

Read those two extremes again. The minimum came back as 2147483647, which is Integer.MAX_VALUE. The maximum came back as -2147483648, which is Integer.MIN_VALUE.

That behaviour is deliberate, and the logic is sound. To find a minimum, you start at the highest possible value and lower it as smaller numbers arrive. If nothing ever arrives, that starting value survives untouched.

8.2 The Table of Defaults

Getter Int variant Long variant Double variant
getCount() 0 0 0
getSum() 0 0 0.0
getMin() 2147483647 9223372036854775807 Infinity
getMax() -2147483648 -9223372036854775808 -Infinity
getAverage() 0.0 0.0 0.0

The double variant is the loudest of the three. It reports Infinity and -Infinity, which at least looks obviously wrong on screen rather than plausibly wrong.

Count and sum stay sensible in every case. The average comes back as 0.0 too, so nothing ever divides by zero.

8.3 Guarding Against It

The fix takes one line. Check the count before you trust the extremes.

IntSummaryStatistics stats = studentList.stream()
        .collect(Collectors.summarizingInt(Student::getMarks));

if (stats.getCount() == 0) {
    System.out.println("No students yet.");
} else {
    System.out.println("Lowest : " + stats.getMin());
    System.out.println("Highest: " + stats.getMax());
}

Make that guard a habit in any code that prints or stores a minimum or maximum. Count and sum you can read straight out; the extremes always deserve the check.

9. Other Ways to Get the Same Numbers

The Collectors class is not the only route to these figures. Two alternatives come up constantly in interviews and code reviews.

9.1 mapToInt and summaryStatistics

Primitive streams carry their own summaryStatistics() method. Map to an IntStream first, then ask it directly.

// Route A: the collector
IntSummaryStatistics viaCollector = studentList.stream()
        .collect(Collectors.summarizingInt(Student::getMarks));

// Route B: the primitive stream
IntSummaryStatistics viaStream = studentList.stream()
        .mapToInt(Student::getMarks)
        .summaryStatistics();

// Both produce identical numbers

Route B reads a little more naturally for a simple case. Route A wins the moment you need the statistics nested inside another collector, which section 10 demonstrates.

9.2 summingInt and averagingInt

When you genuinely need one number, the narrower collectors say so more clearly.

int total = studentList.stream()
        .collect(Collectors.summingInt(Student::getMarks));       // 2620

double mean = studentList.stream()
        .collect(Collectors.averagingInt(Student::getMarks));     // 436.6666666666667

Two details separate them from the summarizing family. summingInt returns an Integer, so it can overflow where getSum() cannot. And averagingInt returns a plain 0.0 on an empty stream, with no way to tell that apart from a genuine average of zero.

Our article on the summing method in the Collectors API goes through that family in detail.

10. Summarizing Inside a Group

Here is where summarizing collectors really earn their place. Because they are Collectors, you can drop them into any collector that accepts a downstream.

10.1 Per-Group Statistics With groupingBy

Pass a summarizing collector as the downstream and every group gets its own full set of numbers.

Map<String, IntSummaryStatistics> byBand = studentList.stream()
        .collect(Collectors.groupingBy(
                student -> student.getMarks() >= 450 ? "TOP" : "REST",
                Collectors.summarizingInt(Student::getMarks)));

System.out.println("TOP  : " + byBand.get("TOP"));
System.out.println("REST : " + byBand.get("REST"));
// Output:
// TOP  : IntSummaryStatistics{count=3, sum=1410, min=450, average=470.000000, max=490}
// REST : IntSummaryStatistics{count=3, sum=1210, min=380, average=403.333333, max=430}

The other three total 1210, which averages to 403.33. All of it comes from one pass over the list. Our guide on grouping in Java 8 covers the classifier side properly.

10.2 Two Sides With partitioningBy

When the split is a simple yes or no, partitioningBy reads better and guarantees both keys exist.

Map<Boolean, IntSummaryStatistics> byPass = studentList.stream()
        .collect(Collectors.partitioningBy(
                student -> student.getMarks() >= 420,
                Collectors.summarizingInt(Student::getMarks)));

System.out.println("Passed : " + byPass.get(true));
System.out.println("Failed : " + byPass.get(false));
// Output:
// Passed : IntSummaryStatistics{count=4, sum=1840, min=430, average=460.000000, max=490}
// Failed : IntSummaryStatistics{count=2, sum=780, min=380, average=390.000000, max=400}

Four students cleared 420 and two did not. Because partitioningBy always fills both keys, neither get call can return null, even when every student passes.

That guarantee is the main reason to prefer it over a grouping with a boolean classifier. Our article on partitioning in Java 8 digs into the difference.

11. Common Mistakes and Pitfalls

Six mistakes cover nearly every summarizing bug we run into during code reviews.

11.1 Squeezing a long Into summarizingInt

A ToIntFunction must return an int, so the compiler rejects a long getter. Developers often silence that with a cast, and the cast destroys large values.

// Wrong: the cast wraps ids above 2147483647 into negatives
Collectors.summarizingInt(product -> (int) product.getProductId());

// Right: match the variant to the type
Collectors.summarizingLong(Product::getProductId);

Treat a cast inside a mapper as a warning sign. It usually means you reached for the wrong variant.

11.2 Using double for Money

Doubles cannot represent every decimal exactly. Adding 0.1 ten times does not land on 1.0, and those tiny gaps become visible on an invoice.

Compensated summation reduces the drift, yet it cannot make a double exact. For real currency work, keep amounts in BigDecimal, or store paise and cents as a long and use summarizingLong.

For a dashboard average or an approximate report, summarizingDouble is fine. Just keep it away from anything a customer gets billed for.

11.3 Reusing a Consumed Stream

A stream works exactly once. Store one in a variable, collect from it twice, and the second call throws.

Stream<Student> stream = studentList.stream();

IntSummaryStatistics a = stream.collect(Collectors.summarizingInt(Student::getMarks));
// IllegalStateException: stream has already been operated upon or closed
IntSummaryStatistics b = stream.collect(Collectors.summarizingInt(Student::getMarks));

Call studentList.stream() fresh each time. The source list stays reusable; only the stream burns out.

11.4 Ignoring NaN in double Data

One NaN ruins every figure in a DoubleSummaryStatistics. Filter suspect values before they reach the collector.

DoubleSummaryStatistics safe = products.stream()
        .filter(product -> !Double.isNaN(product.getPrice()))
        .collect(Collectors.summarizingDouble(Product::getPrice));

Better still, stop NaN entering your objects at all. Validate at the boundary where the data arrives, and the rest of the pipeline stays clean.

12. A Practical Walkthrough

Let us build one small report that uses two variants and a grouping together.

12.1 The Problem

We have a list of sales, each with a region, a unit count, and an amount. The team wants a short summary:

  • How many orders came in, and how many units they covered
  • The biggest single order, measured in units
  • Total revenue and the average order value
  • A revenue breakdown per region

Units are whole numbers and money has decimals, so this needs both summarizingInt and summarizingDouble.

12.2 The Code

package com.javahandson.collectors.summarizing;

import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class SalesReport {

    static class Sale {
        private final String region;
        private final int units;
        private final double amount;

        Sale(String region, int units, double amount) {
            this.region = region;
            this.units = units;
            this.amount = amount;
        }

        public String getRegion() { return region; }
        public int getUnits()     { return units; }
        public double getAmount() { return amount; }
    }

    public static void main(String[] args) {

        List<Sale> sales = Arrays.asList(
                new Sale("North", 12, 1500.00),
                new Sale("South",  8,  900.50),
                new Sale("North", 20, 2400.00),
                new Sale("East",   5,  620.25),
                new Sale("South", 15, 1750.75));

        IntSummaryStatistics unitStats = sales.stream()
                .collect(Collectors.summarizingInt(Sale::getUnits));

        DoubleSummaryStatistics moneyStats = sales.stream()
                .collect(Collectors.summarizingDouble(Sale::getAmount));

        Map<String, DoubleSummaryStatistics> byRegion = sales.stream()
                .collect(Collectors.groupingBy(Sale::getRegion,
                        Collectors.summarizingDouble(Sale::getAmount)));

        if (unitStats.getCount() == 0) {
            System.out.println("No sales recorded.");
            return;
        }

        System.out.println("Orders       : " + unitStats.getCount());
        System.out.println("Total units  : " + unitStats.getSum());
        System.out.println("Biggest order: " + unitStats.getMax() + " units");
        System.out.printf("Revenue      : %.2f%n", moneyStats.getSum());
        System.out.printf("Average sale : %.2f%n", moneyStats.getAverage());

        System.out.println("North revenue: " + byRegion.get("North").getSum());
        System.out.println("South average: " + byRegion.get("South").getAverage());
        System.out.println("East orders  : " + byRegion.get("East").getCount());
    }
}
// Output:
// Orders       : 5
// Total units  : 60
// Biggest order: 20 units
// Revenue      : 7171.50
// Average sale : 1434.30
// North revenue: 3900.0
// South average: 1325.625
// East orders  : 1

12.3 Reading the Report

Check the numbers against the data. The units 12, 8, 20, 5, and 15 add up to 60, and 20 is the largest. Revenue comes to 7171.50, which divided across five orders gives an average of 1434.30.

Region figures fall out of the grouping. North sold 1500.00 plus 2400.00, so 3900.0. South sold 900.50 plus 1750.75, and halving that total gives 1325.625.

Two habits from earlier sections show up in this code. The count guard runs before anything reads a maximum, and printf formats the money rather than trusting toString.

13. Interview Questions

Q: What are Java 8 summarizing methods?

A: They are collectors in java.util.stream.Collectors that compute count, sum, minimum, maximum, and average in a single pass over a stream. The three variants are summarizingInt, summarizingLong, and summarizingDouble, and each returns a matching SummaryStatistics object.

Q: What is the difference between summarizingInt, summarizingLong, and summarizingDouble?

A: Only the numeric type changes. summarizingInt takes a ToIntFunction and returns IntSummaryStatistics. summarizingLong takes a ToLongFunction and returns LongSummaryStatistics. summarizingDouble takes a ToDoubleFunction and returns DoubleSummaryStatistics. Match the variant to the type your getter already returns.

Q: What does IntSummaryStatistics return for an empty stream?

A: Count and sum come back as 0 and the average as 0.0, but getMin() returns Integer.MAX_VALUE (2147483647) and getMax() returns Integer.MIN_VALUE (-2147483648). Always check getCount() before you trust the minimum or maximum.

Q: Why does IntSummaryStatistics.getSum() return a long instead of an int?

A: To avoid overflow. An int caps out just above two billion, so adding many int values could wrap into a negative total. Widening the sum to long makes overflow practically impossible for any realistic stream size.

Q: What is the difference between summarizingInt and summingInt?

A: summingInt gives you one number and returns an Integer, which can overflow. summarizingInt gives you all five figures in one object, and its sum is a long. Use summingInt when you truly need only the total, and summarizingInt whenever you need two or more figures.

Q: Can I use summary statistics with groupingBy?

A: Yes. Pass Collectors.summarizingInt(…) as the downstream collector and every group gets its own statistics object. The same works with partitioningBy, which additionally guarantees that both the true and false keys exist.

Q: Is IntSummaryStatistics thread-safe?

A: No. The class carries no synchronisation, so two threads calling accept() on one shared instance will corrupt it. A parallel stream is still safe, because the framework gives each thread a private instance and merges them with combine().

Q: What is the difference between Collectors.summarizingInt and IntStream.summaryStatistics?

A: Both produce identical numbers in a single pass. After a mapToInt call, summaryStatistics() reads more naturally. Collectors.summarizingInt is the only option when you need to nest the statistics inside groupingBy or partitioningBy.

Q: Why does DoubleSummaryStatistics print six decimal places?

A: Its toString method formats the numbers with %f, and %f always shows six decimals. The stored values are ordinary doubles. Read them with the getters and format the output yourself using printf or DecimalFormat.

Q: What happens if one of the double values is NaN?

A: The sum, average, minimum, and maximum all become NaN. A single bad value poisons every figure, so filter with Double.isNaN() before collecting, or validate the data where it enters your system.

Q: Should I use summarizingDouble for money?

A: Not for anything a customer gets billed for. A double cannot represent every decimal exactly, and small errors accumulate. Use BigDecimal for currency, or store the smallest unit such as paise or cents as a long and use summarizingLong.

Q: How do I merge two IntSummaryStatistics objects?

A: Call combine() on one and pass the other. Counts and sums add together, and the minimum and maximum stretch to cover both. The call mutates the receiver and leaves the argument unchanged, which is exactly how parallel streams merge partial results.

14. Conclusion

Let us wrap up what we covered. Java 8 summarizing methods pull count, sum, minimum, maximum, and average out of a stream in one pass, instead of five.

Three variants cover the numeric types. Pick summarizingInt for int fields, summarizingLong for ids and timestamps, and summarizingDouble for prices and percentages. The mapper interface and the returned class are the only things that change.

Watch two type details. The int variant widens its sum to a long so it cannot overflow, while the long variant has nowhere wider to go and still can.

Above all, remember the empty stream. Minimum and maximum come back as the extreme values of their type, not as zero, so guard every report with a getCount() check.

Further Reading

Leave a Comment