How do I calculate a moving average with Stream Gatherers in Java 24?

A moving average smooths out short-term changes in a series of readings. Instead of averaging all the readings once, you average the first few readings, move one position forward, and repeat.

In this example, you’ll calculate an average for every three consecutive temperature readings using Java’s Gatherers.windowSliding().

Stream Gatherers became a standard feature in Java 24, so no preview flags or external libraries are needed. See the Java 24 Gatherers API.

1. Group the readings into overlapping windows

Start with five readings: 18, 21, 24, 27, 23. A window size of three gives you [18, 21, 24] first. The next window drops 18 and adds 27, giving you [21, 24, 27].

The call .gather(Gatherers.windowSliding(3)) turns a stream of individual readings into a stream of these lists.

2. Keep complete windows and calculate each average

If the input has one or two readings, windowSliding(3) produces a single shorter window. The filter() below skips that window so every reported average uses exactly three readings. Empty input produces no windows.

For each remaining list, mapToInt() creates a stream of integers and average() computes the mean. The result is present because every window that reaches this step contains three values.

3. Compile and run the example

Save this file as MovingAverageExample.java:

import java.util.List;
import java.util.Locale;
import java.util.stream.Gatherers;

public class MovingAverageExample {
    public static void main(String[] args) {
        List<Integer> readings = List.of(18, 21, 24, 27, 23);
        int windowSize = 3;

        readings.stream()
                .gather(Gatherers.windowSliding(windowSize))
                .filter(window -> window.size() == windowSize)
                .forEachOrdered(window -> {
                    double average = window.stream()
                            .mapToInt(Integer::intValue)
                            .average()
                            .orElseThrow();

                    System.out.printf(Locale.ROOT, "%s -> %.2f%n",
                            window, average);
                });
    }
}

From the directory containing the file, run:

javac --release 24 MovingAverageExample.java
java MovingAverageExample

Output:

[18, 21, 24] -> 21.00
[21, 24, 27] -> 24.00
[24, 27, 23] -> 24.67

The last window averages 74 / 3. The %.2f format rounds the displayed value to two decimal places; it does not change the stored average.

Best practices

  • Put readings in chronological order before making windows. The gatherer follows the stream’s existing order.
  • Use a positive window size. If it comes from user input, validate it before creating the gatherer.
  • Decide what to do when there are too few readings. This example waits for a full window; if you need early results, define how partial averages should work.
  • A three-reading window counts samples. If your readings arrive at irregular intervals and you need a three-minute average, group them by timestamps instead.