The Streams API in Java: Process Collections the Modern Way
Learn the Java Streams API — filter, map, sorted, collect and more. Turn bulky loops into clean, readable data pipelines, with clear beginner examples.
If lambdas were the appetiser, the Streams API is the main course of modern Java. Streams let you process collections of data in a clean, readable, "pipeline" style — filtering, transforming, and collecting results without writing bulky loops. Once you're comfortable with streams, a lot of everyday data work becomes a joy to write. This is one of the most loved features in modern Java, so let's dig in.
The idea: a pipeline of operations
A stream is a flow of elements you push through a series of steps. Instead of writing a loop that does everything at once, you describe what you want step by step: "take this list → keep only the even numbers → double each → collect the results." Each step is clear and self-contained.
Loop vs stream — the same task
Let's get the even numbers from a list and double them. First the traditional loop, then the stream:
// traditional loop List<Integer> result = new ArrayList<>(); for (int n : numbers) { if (n % 2 == 0) { result.add(n * 2); } }
// the stream way — reads like a sentence List<Integer> result = numbers.stream() .filter(n -> n % 2 == 0) // keep evens .map(n -> n * 2) // double them .collect(Collectors.toList()); // gather into a list
Same result, but the stream version reads top-to-bottom like plain English: "stream the numbers, filter evens, map to double, collect to a list." Those lambdas from the last lesson are what power each step.
The three stages of a stream
- Create the stream — usually
collection.stream(). - Intermediate operations — transform/filter it; you can chain as many as you like. Each returns another stream. (
filter,map,sorted,distinct,limit) - Terminal operation — produces a final result and ends the pipeline. (
collect,forEach,count,sum)
The operations you'll use most
| Operation | What it does |
|---|---|
filter(...) |
Keep only elements that pass a test |
map(...) |
Transform each element into something else |
sorted() |
Sort the elements |
collect(...) |
Gather results into a List/Set/Map |
forEach(...) |
Do something with each element |
count() |
Count the elements |
A few real examples
List<String> names = List.of("Asha", "Ravi", "Sam", "Anita"); // names starting with "A", uppercased List<String> aNames = names.stream() .filter(n -> n.startsWith("A")) .map(String::toUpperCase) .collect(Collectors.toList()); // [ASHA, ANITA] // how many names are longer than 3 letters? long count = names.stream().filter(n -> n.length() > 3).count(); // 3 // sum a list of numbers int total = List.of(1, 2, 3, 4).stream().mapToInt(Integer::intValue).sum(); // 10
Notice String::toUpperCase — that's a method reference, a neat shorthand for a lambda that just calls one method. Read it as "for each, call toUpperCase."
filter and map with no collect/forEach/count at the end, nothing runs — streams are "lazy." If your stream code seems to do nothing, check that it ends with a terminal operation.Streams genuinely changed how I write Java day to day. Loops that used to sprawl over eight lines with a temporary list and an if-statement collapse into three clear, chained steps — and I can read the intent at a glance months later. On this website, streams are everywhere: filtering published posts, transforming entities into display objects, collecting results. Start using them for your list-processing and you won't look back — just remember to end every pipeline with a terminal operation.
Key takeaways
- A stream processes a collection through a readable pipeline of steps.
- Three stages: create (
.stream()) → intermediate ops (filter,map,sorted) → terminal op (collect,count,forEach). - Lambdas power each step; method references (
String::toUpperCase) are a handy shorthand. - Nothing runs without a terminal operation — streams are lazy.
← Previous: Lesson 27 — Lambdas & Functional Interfaces
Next: Lesson 29 — Optional in Java →