Java Streams

Java streams let you process collections in a declarative style: you describe what you want (filter these, transform those, add them up) rather than writing the loop by hand. The Stream API was added in Java 8 and works hand in hand with lambdas.

How a Stream Pipeline Works

  • Source: a collection, array or generator, for example list.stream().
  • Intermediate operations: filter, map, sorted, distinct, limit. They are lazy and return a new stream.
  • Terminal operation: toList, forEach, count, sum, collect, reduce. It runs the pipeline and produces a result.
  • Streams do not modify the original collection and can be used only once.

filter, map and toList

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("sana", "ahmed", "li", "fatima", "bo");
        List<String> result = names.stream()
                .filter(n -> n.length() > 2)
                .map(n -> n.substring(0, 1).toUpperCase() + n.substring(1))
                .sorted()
                .toList();
        System.out.println(result);
        System.out.println(names.stream().filter(n -> n.length() <= 2).count());
    }
}
[Ahmed, Fatima, Sana]
2

toList() (Java 16+) returns an unmodifiable list. In older code you will see .collect(Collectors.toList()).

Numbers: sum, average and reduce

import java.util.List;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<Integer> prices = List.of(120, 80, 45, 300);
        int total = prices.stream().mapToInt(Integer::intValue).sum();
        double avg = prices.stream().mapToInt(Integer::intValue).average().orElse(0);
        int product = IntStream.rangeClosed(1, 5).reduce(1, (a, b) -> a * b);
        System.out.println(total);
        System.out.println(avg);
        System.out.println(product);
    }
}
545
136.25
120

Grouping with Collectors

Collectors turn a stream into maps, joined strings and summaries. groupingBy is especially powerful for reports.

import java.util.*;
import java.util.stream.Collectors;

record Order(String customer, String city, double amount) {}

public class Main {
    public static void main(String[] args) {
        List<Order> orders = List.of(
            new Order("Ali", "Lahore", 250),
            new Order("Mia", "London", 400),
            new Order("Omar", "Lahore", 150),
            new Order("Eva", "London", 100)
        );
        Map<String, Double> byCity = orders.stream()
                .collect(Collectors.groupingBy(Order::city, TreeMap::new,
                         Collectors.summingDouble(Order::amount)));
        System.out.println(byCity);

        String big = orders.stream()
                .filter(o -> o.amount() >= 200)
                .map(Order::customer)
                .collect(Collectors.joining(", ", "Big spenders: ", ""));
        System.out.println(big);

        Optional<Order> top = orders.stream().max(Comparator.comparingDouble(Order::amount));
        System.out.println(top.map(Order::customer).orElse("none"));
    }
}
{Lahore=400.0, London=500.0}
Big spenders: Ali, Mia
Mia

Streams vs Loops

Streams shine for transforming and summarising data and read almost like a sentence. Plain loops are still better when you need early exits with complex logic, index manipulation, or checked exceptions. Both are valid Java; choose whichever is clearer.

Frequently asked questions

Are Java streams faster than for loops?

Not usually. For simple work a loop is often slightly faster. Streams are chosen for readability, and parallel streams can help only with large, CPU-heavy workloads.

What is the difference between map and flatMap?

map turns each element into exactly one new element. flatMap turns each element into a stream and flattens all of them into a single stream, for example from a list of lists to one list.

Can I reuse a Java stream?

No. After a terminal operation the stream is consumed, and using it again throws IllegalStateException. Create a new stream from the source instead.