Sitemap

Java Stream API Tutorial: Filter, Map, Collect, Reduce and Examples for Beginners

14 min readNov 3, 2026

--

Press enter or click to view image in full size
Photo by Emile Perron on Unsplash

Java developers regularly work with collections such as ArrayList, HashSet, and HashMap. In real-world applications, they often need to filter records, transform data, remove duplicates, sort information, and calculate results.

Traditionally, developers perform these operations using loops and conditional statements. However, Java provides another powerful approach: the Java Stream API.

Introduced in Java 8, the Stream API allows developers to process collections of data using a clean, functional programming style.

For example, imagine an employee management application containing hundreds of employee records. You need to find employees earning more than ₹40,000, extract their names, sort them alphabetically, and display the result.

Instead of writing multiple loops, you can perform these operations using a Stream pipeline.

If you are preparing for a Java developer interview or learning Java Full Stack development, understanding the Java Stream API is an important skill.

In this complete Java Stream API tutorial, you will learn:

  • What Java Stream API is and why it is used.
  • How to create streams in Java.
  • Intermediate and terminal operations.
  • Filter, map, flatMap, distinct, sorted, and reduce.
  • Collectors and groupingBy.
  • Optional in Java Streams.
  • Stream API vs traditional loops.
  • Common mistakes and interview questions.
  • Practical Java Stream API examples.

What Is the Java Stream API?

The Java Stream API is a feature introduced in Java 8 that allows developers to process sequences of data using functional-style operations.

A Stream represents a sequence of elements that can be processed through a pipeline of operations.

It supports operations such as:

  • Filtering elements.
  • Transforming data.
  • Sorting records.
  • Removing duplicate values.
  • Calculating totals and averages.
  • Grouping information.
  • Finding matching elements.

Important: A Java Stream is not a data structure. It does not store elements like an ArrayList or HashSet. Instead, it processes data from a source such as a collection, array, or generator.

For example:

import java.util.Arrays;
import java.util.List;
public class StreamExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40, 50);
numbers.stream()
.filter(n -> n > 25)
.forEach(System.out::println);
}
}

Output:

30
40
50

In this example:

  • stream() creates a stream from the list.
  • filter() selects numbers greater than 25.
  • forEach() prints the selected values.

This is a simple example of Java Stream API data processing.

Why Is the Java Stream API Important?

The Stream API helps developers write readable and maintainable code when processing data.

Some important advantages include:

  1. Less repetitive code: Stream operations can replace many repetitive loops and conditional statements.
  2. Functional programming: Developers can use lambda expressions and functional interfaces to describe data-processing operations.
  3. Easy data filtering: Select elements based on specific conditions.
  4. Data transformation: Convert one type of data into another using map().
  5. Aggregation: Calculate totals, averages, minimum values, and maximum values.
  6. Parallel processing: Parallel streams can process suitable workloads using multiple threads.
  7. Readable pipelines: Multiple operations can be connected into a single processing pipeline.

However, streams are not automatically faster than loops. Choose the approach that makes your code correct, readable, and suitable for the workload.

How Does the Java Stream API Work?

A Stream pipeline generally consists of three parts:

  1. Source
  2. Intermediate operations
  3. Terminal operation

Let’s understand each part.

1. Stream Source

A source provides the data that needs to be processed.

Examples include:

  • ArrayList
  • HashSet
  • Arrays
  • Stream.of()
  • Files and other supported data sources

2. Intermediate Operations

Intermediate operations transform a stream into another stream.

Common examples include:

  • filter()
  • map()
  • flatMap()
  • distinct()
  • sorted()
  • limit()
  • skip()

These operations are generally lazy. They describe what should happen but do not process the elements until a terminal operation begins.

3. Terminal Operations

Terminal operations trigger stream processing and produce a result or side effect.

Examples include:

  • collect()
  • forEach()
  • count()
  • reduce()
  • findFirst()
  • anyMatch()

A simple Stream pipeline looks like this:

Collection
|
v
stream()
|
v
filter()
|
v
map()
|
v
sorted()
|
v
collect()
|
v
Final Result

Understanding this structure makes Java Stream API examples easier to read and implement.

How to Create a Stream in Java

There are different ways to create a Java Stream depending on the data source.

1. Creating a Stream from a Collection

The most common approach is using the stream() method.

import java.util.ArrayList;
import java.util.List;
public class CreateStreamExample {    public static void main(String[] args) {        List<String> names = new ArrayList<>();        names.add("Amit");
names.add("Priya");
names.add("Rahul");
names.stream()
.forEach(System.out::println);
}
}

2. Creating a Stream Using Stream.of()

The Stream.of() method creates a stream from specified values.

import java.util.stream.Stream;
public class StreamOfExample {    public static void main(String[] args) {        Stream<String> languages =
Stream.of("Java", "Python", "JavaScript");
languages.forEach(System.out::println);
}
}

3. Creating a Stream from an Array

You can use Arrays.stream() to create a stream from an array.

import java.util.Arrays;
public class ArrayStreamExample {    public static void main(String[] args) {        int[] numbers = {10, 20, 30, 40};        Arrays.stream(numbers)
.forEach(System.out::println);
}
}

4. Creating an IntStream

Java provides specialized stream types for primitive values, including IntStream, LongStream, and DoubleStream.

import java.util.stream.IntStream;
public class IntStreamExample {    public static void main(String[] args) {        IntStream.rangeClosed(1, 5)
.forEach(System.out::println);
}
}

Output:

1
2
3
4
5

IntStream is useful when processing primitive integer values without unnecessarily boxing every value into an Integer object.

Intermediate Operations in Java Stream API

Intermediate operations are used to filter, transform, sort, and modify the stream pipeline.

Let’s understand the most important Java Stream methods with examples.

1. filter() Method in Java

The filter() method selects elements that satisfy a given condition.

It accepts a Predicate and returns a new stream containing matching elements.

Example: Filter Even Numbers

import java.util.Arrays;
import java.util.List;
public class FilterExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 15, 20, 25, 30, 35);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
}
}

Output:

10
20
30

Here, the filter() method selects only even numbers.

2. map() Method in Java

The map() method transforms each stream element into another value.

It accepts a Function and produces a new stream.

Example: Convert Names to Uppercase

import java.util.Arrays;
import java.util.List;
public class MapExample {    public static void main(String[] args) {        List<String> names =
Arrays.asList("amit", "priya", "rahul");
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
}
}

Output:

AMIT
PRIYA
RAHUL

The map() method transforms every name into uppercase.

3. flatMap() Method in Java

The flatMap() method transforms each element into a stream and combines those streams into one stream.

It is particularly useful when working with nested collections.

Example: Convert Nested Lists into a Single List

import java.util.Arrays;
import java.util.List;
public class FlatMapExample {    public static void main(String[] args) {        List<List<String>> groups = Arrays.asList(
Arrays.asList("Java", "SQL"),
Arrays.asList("Spring", "Hibernate"),
Arrays.asList("HTML", "CSS")
);
groups.stream()
.flatMap(List::stream)
.forEach(System.out::println);
}
}

Output:

Java
SQL
Spring
Hibernate
HTML
CSS

The flatMap() method converts nested lists into a single stream of elements.

4. distinct() Method in Java

The distinct() method removes duplicate elements from a stream according to their equality.

Example: Remove Duplicate Numbers

import java.util.Arrays;
import java.util.List;
public class DistinctExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 10, 30, 20, 40);
numbers.stream()
.distinct()
.forEach(System.out::println);
}
}

Output:

10
20
30
40

5. sorted() Method in Java

The sorted() method sorts elements in natural order or using a supplied Comparator.

Example: Sort Numbers

import java.util.Arrays;
import java.util.List;
public class SortedExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(50, 10, 40, 20, 30);
numbers.stream()
.sorted()
.forEach(System.out::println);
}
}

Output:

10
20
30
40
50

For descending order:

numbers.stream()
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);

6. limit() and skip() Methods

The limit() method restricts the number of elements in a stream.

The skip() method skips a specified number of elements.

import java.util.Arrays;
import java.util.List;
public class LimitSkipExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40, 50);
numbers.stream()
.limit(3)
.forEach(System.out::println);
System.out.println("After skipping:"); numbers.stream()
.skip(2)
.forEach(System.out::println);
}
}

Output:

10
20
30
After skipping:
30
40
50

These methods are useful for limiting results and processing selected portions of a collection.

Terminal Operations in Java Stream API

Terminal operations complete the stream pipeline.

They trigger processing and return a result or perform an action.

1. forEach() Method

The forEach() method performs an action for each element.

import java.util.Arrays;
import java.util.List;
public class ForEachExample {    public static void main(String[] args) {        List<String> skills =
Arrays.asList("Java", "SQL", "Spring Boot");
skills.stream()
.forEach(skill -> System.out.println(skill));
}
}

2. collect() Method

The collect() method gathers stream elements into a result such as a List, Set, or Map using a Collector.

Example: Collect Filtered Numbers into a List

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CollectExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 15, 20, 25, 30);
List<Integer> result =
numbers.stream()
.filter(n -> n > 20)
.collect(Collectors.toList());
System.out.println(result);
}
}

Output:

[25, 30]

For Java 16 and later, you can also use the toList() terminal operation:

List<Integer> result =
numbers.stream()
.filter(n -> n > 20)
.toList();

Note that Stream.toList() returns an unmodifiable list. Collectors.toList() does not guarantee a particular implementation or mutability contract.

3. count() Method

The count() method returns the number of elements in a stream.

import java.util.Arrays;
import java.util.List;
public class CountExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40, 50);
long count =
numbers.stream()
.filter(n -> n > 20)
.count();
System.out.println(count);
}
}

Output:

3

4. reduce() Method

The reduce() method combines stream elements into a single result.

Get Vaishnavi Desale’s stories in your inbox

Join Medium for free to get updates from this writer.

It is useful for operations such as calculating totals and combining values.

Example: Calculate the Sum of Numbers

import java.util.Arrays;
import java.util.List;
public class ReduceExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40);
int sum =
numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum);
}
}

Output:

100

The value 0 is the identity value, and Integer::sum combines the elements.

5. findFirst() Method

The findFirst() method returns the first element of a stream as an Optional.

import java.util.Arrays;
import java.util.List;
public class FindFirstExample {    public static void main(String[] args) {        List<String> names =
Arrays.asList("Amit", "Priya", "Rahul");
names.stream()
.filter(name -> name.startsWith("P"))
.findFirst()
.ifPresent(System.out::println);
}
}

Output:

Priya

6. anyMatch(), allMatch(), and noneMatch()

These methods check whether elements satisfy a particular condition.

MethodPurposeanyMatch()Checks whether at least one element matchesallMatch()Checks whether every element matchesnoneMatch()Checks whether no elements match

Example:

import java.util.Arrays;
import java.util.List;
public class MatchExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40);
boolean result =
numbers.stream()
.anyMatch(n -> n > 30);
System.out.println(result);
}
}

Output:

true

These operations can stop evaluating once the result is determined.

What Is the Difference Between map() and flatMap()?

The difference between map() and flatMap() is frequently asked in Java 8 interviews.

Featuremap()flatMap()PurposeTransforms elementsTransforms and flattens elementsMappingOne input to one outputOne input can produce multiple outputsResultStream of mapped valuesCombined stream of mapped valuesCommon useConvert names to uppercaseFlatten nested lists

Example:

List<String> names =
Arrays.asList("Amit", "Priya");
List<String> result =
names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());

The map() method transforms each name.

When working with nested lists, flatMap() helps combine the nested elements into one stream.

What Is the Collectors Class in Java?

The Collectors class provides utility methods used with the Stream API to collect and organize processed data.

It belongs to the java.util.stream package.

Some commonly used Collectors methods include:

MethodPurposetoList()Collects elements into a ListtoSet()Collects elements into a SettoMap()Collects elements into a Mapjoining()Joins stringsgroupingBy()Groups elements based on a classificationpartitioningBy()Divides elements into two groupscounting()Counts grouped elementsaveragingInt()Calculates an integer average

Example: Grouping Employees by Department

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Employee {    private String name;
private String department;
Employee(String name, String department) {
this.name = name;
this.department = department;
}
public String getName() {
return name;
}
public String getDepartment() {
return department;
}
}
public class GroupingExample { public static void main(String[] args) { List<Employee> employees = Arrays.asList(
new Employee("Amit", "IT"),
new Employee("Priya", "HR"),
new Employee("Rahul", "IT"),
new Employee("Neha", "HR")
);
Map<String, List<Employee>> groupedEmployees =
employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment
));
groupedEmployees.forEach((department, records) -> {
System.out.println(department);
records.forEach(employee ->
System.out.println(employee.getName())
);
});
}
}

Possible output:

HR
Priya
Neha
IT
Amit
Rahul

The order of department groups is not guaranteed because the default groupingBy collector uses a HashMap.

This example demonstrates how Java Stream API and Collectors can simplify data grouping in employee management applications.

What Is Optional in Java Streams?

Optional is a container that may hold a non-null value or be empty.

Some Stream operations, including findFirst(), findAny(), min(), and max(), return Optional because the stream might not contain a matching element.

Example:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class OptionalExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40);
Optional<Integer> result =
numbers.stream()
.filter(n -> n > 50)
.findFirst();
System.out.println(result.orElse(0));
}
}

Output:

0

No number matches the condition, so the Optional is empty and orElse(0) provides a default value.

Understanding Optional is useful when writing Java Stream API code that searches for elements.

Java Stream API vs Traditional For Loop

Both traditional loops and streams can process collections, but they provide different programming approaches.

FeatureTraditional For LoopJava Stream APIProgramming styleImperativeFunctional-styleCode structureExplicit iterationPipeline-based processingFilteringUses conditionsUses filter()TransformationUses assignmentsUses map()AggregationUses manual calculationsUses reduce() and collectorsParallel processingRequires additional coordinationSupports parallel streamsReadabilityDepends on implementationOften concise for data pipelines

Example: Traditional For Loop

import java.util.Arrays;
import java.util.List;
public class LoopExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40);
for (Integer number : numbers) { if (number > 20) {
System.out.println(number);
}
}
}
}

Same Example Using Stream API

numbers.stream()
.filter(number -> number > 20)
.forEach(System.out::println);

Both approaches produce the same result.

Use streams when they make the processing logic easier to understand. Traditional loops may be better when you need complex control flow, early exits, or straightforward stateful processing.

What Are Parallel Streams in Java?

Parallel streams allow a stream pipeline to process elements using multiple threads.

They are useful for certain independent data-processing tasks, especially when the workload is sufficiently large and suitable for parallel execution.

Example:

import java.util.Arrays;
import java.util.List;
public class ParallelStreamExample {    public static void main(String[] args) {        List<Integer> numbers =
Arrays.asList(10, 20, 30, 40, 50);
numbers.parallelStream()
.forEach(System.out::println);
}
}

The output order is not guaranteed because elements may be processed by different threads.

Stream vs Parallel Stream

FeatureStreamParallel StreamProcessingSequential by defaultUses parallel executionThread usageUsually current threadUses multiple threadsExecution orderPreserves encounter order for ordered operations where specifiedSome operations may not preserve processing orderPerformanceSuitable for general processingCan help with suitable large workloadsComplexityUsually easier to reason aboutRequires attention to shared state and thread safety

Important: Parallel streams are not always faster. Small tasks, shared mutable data, blocking operations, and thread-management overhead can reduce performance.

Common Mistakes Beginners Make When Using Java Streams

Understanding Java Stream API methods is important, but knowing common mistakes is equally useful.

1. Reusing a Stream

A stream should be operated on only once.

Incorrect example:

Stream<String> stream =
names.stream();
stream.forEach(System.out::println);stream.count();

The second operation attempts to reuse an already consumed stream.

Create a new stream from the source when another pipeline is needed.

2. Forgetting the Terminal Operation

Intermediate operations are lazy.

For example:

names.stream()
.filter(name -> name.startsWith("A"));

This code creates a pipeline but does not trigger processing because there is no terminal operation.

Add an operation such as collect(), count(), or forEach() when you want to execute it.

3. Modifying the Source During Stream Processing

Avoid modifying the original collection while processing it through a stream pipeline.

This can lead to unexpected behavior or exceptions.

4. Using Parallel Streams Without Understanding Them

Parallel processing introduces additional complexity. Avoid assuming that parallelStream() will always improve performance.

5. Using peek() for Business Logic

The peek() method is primarily intended for debugging. Do not depend on its side effects to implement important application behavior.

6. Ignoring Optional Results

Operations such as findFirst() may return an empty Optional. Handle that possibility instead of assuming a value always exists.

Java Stream API Interview Questions for Freshers

If you are preparing for a Java developer interview, practise these commonly asked Java 8 Stream API questions.

1. What is the Stream API in Java?

The Stream API is a Java feature introduced in Java 8 for processing sequences of data using functional-style operations.

2. What is the difference between a Stream and a Collection?

A Collection stores elements, whereas a Stream processes elements from a data source through a pipeline.

3. What are intermediate operations in Java Streams?

Intermediate operations return another stream. Examples include filter(), map(), flatMap(), distinct(), and sorted().

4. What are terminal operations in Java Streams?

Terminal operations trigger stream processing and produce a result or side effect. Examples include collect(), count(), reduce(), and forEach().

5. What is the difference between map() and filter()?

map() transforms elements, while filter() selects elements based on a condition.

6. What is flatMap() in Java?

flatMap() transforms elements into streams and combines the resulting elements into one stream.

7. What is the difference between findFirst() and findAny()?

findFirst() returns the first element in encounter order when one exists. findAny() returns some element and is useful when any matching element is acceptable.

8. What is lazy evaluation in Java Streams?

Lazy evaluation means intermediate operations do not process elements until a terminal operation is initiated.

9. Can we reuse a Java Stream?

No. A stream should be operated on only once. Create a new stream for another pipeline.

10. What is the purpose of reduce()?

reduce() combines stream elements into a single result using an accumulation operation.

11. What is Collectors.groupingBy()?

It groups stream elements based on a classification function and returns a Map.

12. What is the difference between stream() and parallelStream()?

stream() creates a sequential stream, while parallelStream() creates a stream intended for parallel processing.

13. What is Optional in Java?

Optional is a container that may contain a non-null value or be empty. It helps represent potentially absent results.

14. What is the difference between toList() and Collectors.toList()?

Stream.toList() was introduced in Java 16 and returns an unmodifiable list. Collectors.toList() is available from Java 8 and does not guarantee a specific list implementation or mutability.

Java Stream API Practice Programs for Freshers

Reading Java Stream API examples is useful, but solving coding exercises helps you understand how to apply the methods.

Try these beginner-friendly Java 8 Stream API programs:

  1. Find all even numbers from a list using filter().
  2. Convert a list of names into uppercase using map().
  3. Remove duplicate elements using distinct().
  4. Sort a list of integers using sorted().
  5. Find the sum of numbers using reduce().
  6. Count elements that satisfy a condition using count().
  7. Find the first number greater than 50 using findFirst().
  8. Convert a nested list into a single list using flatMap().
  9. Group employees by department using groupingBy().
  10. Find the maximum and minimum number using max() and min().
  11. Calculate the average of numbers using averagingInt().
  12. Join a list of strings using Collectors.joining().
  13. Partition employees based on salary using partitioningBy().
  14. Find whether any employee belongs to a particular department using anyMatch().

Try completing these Java Stream API coding questions without looking at the solutions.

How Java Stream API Is Used in Java Full Stack Development

Java Stream API is not limited to interview examples. It is also useful in backend application development.

Consider a Java Full Stack application that manages employee records.

The backend may need to:

  • Filter employees based on department.
  • Extract employee names from database records.
  • Calculate average salaries.
  • Group students according to their courses.
  • Remove duplicate values from application data.
  • Transform information before returning REST API responses.

Java Streams can make many of these data-processing tasks easier to express.

In Spring Boot applications, developers commonly use streams when processing collections returned by service or repository methods.

However, Stream API operations do not replace database queries. For large datasets, filtering and aggregation at the database level may be more efficient than loading every record into application memory.

If you are building your Java development foundation, explore the Java Full Stack Development course at VibrantMinds.

VibrantMinds Technologies Pvt. Ltd., Pune, focuses on Java Full Stack training and placement support. Visit the official VibrantMinds website to learn more.

Conclusion

The Java Stream API is an important feature for developers who want to process collections and work with data efficiently.

Start by understanding how to create streams and build pipelines. Then practise intermediate operations such as filter(), map(), flatMap(), distinct(), and sorted().

Next, learn terminal operations such as collect(), reduce(), count(), and findFirst(). Once you understand these fundamentals, explore Collectors, Optional, groupingBy(), and parallel streams.

Do not simply memorize Java 8 Stream API interview questions. Write practical programs, work with employee and student records, and understand how each operation transforms data.

A strong understanding of Java Streams can improve your Core Java skills and help you prepare for Java developer interviews and Java Full Stack development.

Frequently Asked Questions About Java Stream API

What is the Java Stream API used for?

The Java Stream API is used to process collections and other data sources through operations such as filtering, mapping, sorting, grouping, and reducing.

Which Java version introduced the Stream API?

The Stream API was introduced in Java 8.

What are the most commonly used Stream API methods?

Common methods include filter(), map(), flatMap(), sorted(), distinct(), collect(), reduce(), count(), and forEach().

What is the difference between filter() and map()?

filter() selects elements based on a condition, while map() transforms each element into another value.

Can Java Streams modify the original collection?

Stream operations do not normally modify the source collection. Avoid modifying a stream’s source while its pipeline is executing.

Are Java Streams reusable?

No. A stream should be operated on only once. Create a new stream for another processing operation.

Is the Java Stream API important for freshers?

Yes. It is an important Java 8 feature and is useful for programming assignments, coding interviews, and backend application development.

What should I learn after Java Stream API?

After learning Java Streams, strengthen your knowledge of Optional, lambda expressions, functional interfaces, multithreading, JDBC, Spring, and Spring Boot.

--

--