LearnFrenzy
Sign in Take a free test

Generics, Lambdas and Streams · Lesson 36 of 42

The Stream API

6 min read Updated Nov 7, 2026 Saurabh Samir

Streams let you describe what you want from a collection – "the names of IT employees earning over ₹80,000, sorted" – instead of writing the loops that do it. The code reads like a sentence, and stream questions are now a standard part of Java interviews.

In this lesson

  • How a stream pipeline works
  • Intermediate operations: filter, map, sorted, distinct, limit
  • Terminal operations: collect, reduce, count, anyMatch
  • Collectors: toList, groupingBy, counting, joining – and flatMap

A pipeline

import java.util.List;

public class Main {
    record Employee(String name, String dept, double salary) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Asha", "IT", 85000), new Employee("Ben", "HR", 60000),
            new Employee("Chen", "IT", 92000), new Employee("Dia", "Sales", 70000),
            new Employee("Eli", "IT", 78000));

        List<String> result = staff.stream()                 // 1. source
            .filter(e -> e.dept().equals("IT"))                // 2. keep IT
            .filter(e -> e.salary() > 80000)                   //    earning over 80k
            .map(Employee::name)                              // 3. transform to names
            .sorted()                                         //    sort
            .toList();                                        // 4. terminal: collect
        System.out.println(result);
    }
}

Output

[Asha, Chen]

source → intermediate ops → terminal op

  1. Sourcelist.stream(), Arrays.stream(arr), Stream.of(...)
  2. Intermediate (lazy)filter, map, sorted, distinct, limit – return a new stream
  3. TerminaltoList, collect, reduce, count, forEach – runs the pipeline
  • Streams don't change the source collection.
  • Intermediate operations are lazy: nothing runs until a terminal operation is called.
  • A stream can be consumed only once.

Common operations

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

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(5, 3, 8, 3, 9, 1, 8, 6);
        System.out.println(nums.stream().distinct().sorted().toList());
        System.out.println(nums.stream().map(n -> n * n).limit(3).toList());
        System.out.println(nums.stream().filter(n -> n % 2 == 0).count());
        System.out.println(nums.stream().mapToInt(Integer::intValue).sum());
        System.out.println(nums.stream().mapToInt(n -> n).average().getAsDouble());
        System.out.println(nums.stream().max(Integer::compare).get());
        System.out.println(nums.stream().anyMatch(n -> n > 8) + " " + nums.stream().allMatch(n -> n > 0));
        System.out.println(nums.stream().reduce(0, Integer::sum));
        System.out.println(IntStream.rangeClosed(1, 5).boxed().toList());
    }
}

Output

[1, 3, 5, 6, 8, 9]
[25, 9, 64]
3
43
5.375
9
true true
43
[1, 2, 3, 4, 5]

Collectors: grouping and summarising

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class Main {
    record Employee(String name, String dept, double salary) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Asha", "IT", 85000), new Employee("Ben", "HR", 60000),
            new Employee("Chen", "IT", 92000), new Employee("Dia", "Sales", 70000),
            new Employee("Eli", "IT", 78000), new Employee("Fay", "HR", 64000));

        Map<String, Long> headcount = staff.stream()
            .collect(Collectors.groupingBy(Employee::dept, TreeMap::new, Collectors.counting()));
        System.out.println(headcount);

        Map<String, Double> avgPay = staff.stream()
            .collect(Collectors.groupingBy(Employee::dept, TreeMap::new, Collectors.averagingDouble(Employee::salary)));
        System.out.println(avgPay);

        Map<String, List<String>> names = staff.stream()
            .collect(Collectors.groupingBy(Employee::dept, TreeMap::new,
                     Collectors.mapping(Employee::name, Collectors.toList())));
        System.out.println(names);

        Map<Boolean, Long> above75k = staff.stream()
            .collect(Collectors.partitioningBy(e -> e.salary() > 75000, Collectors.counting()));
        System.out.println(above75k);

        System.out.println(staff.stream().map(Employee::name).collect(Collectors.joining(", ", "[", "]")));
    }
}

Output

{HR=2, IT=3, Sales=1}
{HR=62000.0, IT=85000.0, Sales=70000.0}
{HR=[Ben, Fay], IT=[Asha, Chen, Eli], Sales=[Dia]}
{false=3, true=3}
[Asha, Ben, Chen, Dia, Eli, Fay]

flatMap: flattening nested data

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<List<String>> skills = List.of(List.of("Java", "SQL"), List.of("Java", "Spring"), List.of("Git"));
        System.out.println(skills.stream().flatMap(List::stream).distinct().sorted().toList());

        List<String> lines = List.of("to be or", "not to be");
        long words = lines.stream().flatMap(l -> Arrays.stream(l.split(" "))).count();
        System.out.println(words + " words");
    }
}

Output

[Git, Java, SQL, Spring]
6 words

Interview-style stream questions

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        String s = "programming";
        // 1. Character frequency (in order of first appearance)
        Map<Character, Long> freq = s.chars().mapToObj(c -> (char) c)
            .collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));
        System.out.println(freq);
        // 2. First non-repeated character
        System.out.println(freq.entrySet().stream().filter(e -> e.getValue() == 1).map(Map.Entry::getKey).findFirst().orElse('-'));
        // 3. Second highest distinct number
        List<Integer> nums = List.of(12, 45, 7, 45, 30);
        System.out.println(nums.stream().distinct().sorted((a, b) -> b - a).skip(1).findFirst().orElseThrow());
        // 4. Duplicates
        System.out.println(nums.stream().filter(n -> nums.indexOf(n) != nums.lastIndexOf(n)).distinct().toList());
    }
}

Output

{p=1, r=2, o=1, g=2, a=1, m=2, i=1, n=1}
p
30
[45]

Common mistakes with streams

  • Reusing a stream: a second terminal operation throws IllegalStateException.
  • Changing external variables inside forEach or map – keep lambdas free of side effects and use collectors instead.
  • Using streams for everything: a simple loop is sometimes clearer, especially with break or complex state.
  • parallelStream() is not automatically faster – it helps only for large, CPU-heavy work.

Practice

Try it yourself

  1. From ["apple", "banana", "kiwi", "cherry", "fig"], list words longer than 4 letters in upper case.

    Show solution
    import java.util.List;
    
    public class Main {
        public static void main(String[] args) {
            List<String> fruits = List.of("apple", "banana", "kiwi", "cherry", "fig");
            System.out.println(fruits.stream().filter(f -> f.length() > 4).map(String::toUpperCase).toList());
        }
    }

    Output

    [APPLE, BANANA, CHERRY]
  2. Sum the squares of the odd numbers from 1 to 9.

    Show solution
    import java.util.stream.IntStream;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println(IntStream.rangeClosed(1, 9).filter(n -> n % 2 == 1).map(n -> n * n).sum());
        }
    }

    Output

    165

Quick check

When does a stream pipeline actually run?

Only when a terminal operation (toList, collect, count, forEach…) is called.

What is the difference between map and flatMap?

map transforms each element into one value; flatMap transforms each into a stream and flattens the results into one stream.

Which collector groups elements by a key?

Collectors.groupingBy

Summary

  • A stream is source → lazy intermediate operations → one terminal operation.
  • filter, map, sorted, distinct, limit shape data; collect, reduce, count, anyMatch finish it.
  • groupingBy, partitioningBy, counting, joining and flatMap solve most real reporting tasks.

Interview practice: Java Stream API coding questions.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular