Java 8 Stream Tutorial
Java 8 introduced the Stream API, which allows developers to process collections of data in a functional and declarative way. Streams make it easier to perform operations such as filtering, mapping, reducing and collecting data without writing complex loops.
1. Introduction to Streams
A Stream is a sequence of elements that supports functional-style operations. Unlike Collections, a Stream does not store data it only processes it.

Features of Streams
- Declarative: Write concise and readable code using functional style.
- Lazy Evaluation: Operations are executed only when needed (terminal operation).
- Parallel Execution: Supports parallel streams to leverage multi-core processors.
- Reusable Operations: Supports chaining of operations like map(), filter(), sorted().
- No Storage: Streams donât store data; they only process it.
How does Stream Work Internally?
- Create a Stream: From collections, arrays or static methods.
- Apply Intermediate Operations: Transform data (e.g., filter(), map(), sorted()).
- Apply Terminal Operation: Produce a result (e.g., forEach(), collect(), reduce()).
2. Creation of Streams
Streams Creation can be done by multiple ways:
- From a Collection: Create a stream directly from a List, Set or any Collection using stream()
- From an Array: Use Arrays.stream(array) to convert an array into a stream.
- Using Stream.of(): Create a stream from a fixed set of values using Stream.of().
- Infinite Stream: Generate an unbounded sequence using Stream.iterate() or Stream.generate()
Example
import java.util.*;
import java.util.stream.*;
public class StreamCreation {
public static void main(String[] args) {
// 1. From a Collection
List<String> list = Arrays.asList("Java", "Python", "C++");
Stream<String> stream1 = list.stream();
// 2. From an Array
String[] arr = {"A", "B", "C"};
Stream<String> stream2 = Arrays.stream(arr);
// 3. Using Stream.of()
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);
// 4. Infinite Stream (limit to avoid infinite loop)
Stream<Integer> stream4 = Stream.iterate(1, n -> n + 1).limit(5);
stream4.forEach(System.out::println);
}
}
Output
1 2 3 4 5
To read more about Creation of Stream -> Streams Creation
3. Stream Pipeline
A Stream Pipeline defines how data flows through different stages. It has three parts:
3.1 Source
The source provides the data for the stream. It can be a collection, array, file or even an infinite generator.
Example:
List<Integer> numbers = Arrays.asList(10, 20, 30, 40);
Stream<Integer> stream = numbers.stream(); // Source
3.2 Intermediate Operations
Intermediate operations transform a stream into another stream. Some common intermediate operations include:
- filter(): Filters elements based on a specified condition.
- map(): Transforms each element in a stream to another value.
- Sorted(): Sorts the elements of a stream.
- Distinct(): Remove duplicates.
- Skip(): Skip first n elements.
Example:
import java.util.*;
import java.util.stream.*;
public class StreamIntermediate {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 10, 20, 10, 30, 40);
numbers.stream()
.filter(n -> n > 10) // keep > 10
.map(n -> n * 2) // double them
.distinct() // remove duplicates
.sorted() // sort ascending
.forEach(System.out::println);
}
}
Output
40 60 80
To read more about Intermediate operations-> Intermediate operations
3.3 Terminal Operations
Terminal Operations are the operations that on execution return a final result as an absolute value.
- ForEach(): It iterates all the elements in a stream.
- collect(Collectors.toList()): It collects stream elements into a list (or other collections like set/map).
- Reduce(): It reduces stream elements into a single aggregated result.
- count(): It returns the total number of elements in a stream.
- anyMatch() / allMatch() / noneMatch(): They check whether elements match a given condition.
- findFirst() / findAny(): They return the first or any element from a stream.
Example:
import java.util.*;
import java.util.stream.*;
public class StreamTerminal {
public static void main(String[] args)
{
List<String> names = Arrays.asList("Amit", "Riya", "Rohan", "Amit");
// Collect into Set (removes duplicates)
Set<String> uniqueNames = names.stream().collect(Collectors.toSet());
System.out.println(uniqueNames);
// Count names starting with 'R'
long count = names.stream().filter(n -> n.startsWith("R")).count();
System.out.println("Names starting with R: " + count);
// Reduce (concatenate names)
String result = names.stream().reduce("", (a, b) -> a + b + " ");
System.out.println(result);
}
}
Output
[Amit, Riya, Rohan] Names starting with R: 2 Amit Riya Rohan Amit
4. Types of Streams
Streams can be categorized into different types based on their nature and usage.
4.1 Sequential Stream
- Processes elements one by one in a single thread.
- Created by default when you call stream().
Example:
import java.util.*;
public class SequentialStreamDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("A", "B", "C", "D");
names.stream()
.forEach(System.out::println); // Executes sequentially
}
}
Output
A B C D
4.2 Parallel Streams
Parallel Streams are the type of streams that can perform operations concurrently on multiple threads. These Streams are meant to make use of multiple processors or cores available to speed us the processing speed. There are two methods to create parallel streams are mentioned below:
- Using the parallel() method on a stream
- Using parallelStream() on a Collection
Example:
import java.util.*;
import java.util.stream.*;
public class ParallelStreamDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9);
numbers.parallelStream().forEach(n -> System.out.println(n + " " + Thread.currentThread().getName()));
}
}
Output
2 ForkJoinPool.commonPool-worker-3 1 ForkJoinPool.commonPool-worker-3 4 ForkJoinPool.commonPool-worker-3 5 ForkJoinPool.commonPool-worker-3 7 ForkJoinPool.commonPool-worker-3 6 main 8 ForkJoinPool.com...
To know more about Parallel Streams -> Parallel Streams
4.3 Infinite Streams
Streams can also generate unbounded sequences. Use limit() to avoid infinite execution.
Example:
import java.util.stream.*;
public class InfiniteStreamDemo {
public static void main(String[] args) {
Stream.iterate(1, n -> n + 1)
.limit(5)
.forEach(System.out::println);
}
}
Output
1 2 3 4 5
4.4 Primitive Streams
Java provides specialized streams for primitive data types:
- IntStream -> for int values
- LongStream -> for long values
- DoubleStream -> for double values
Example:
import java.util.stream.IntStream;
public class PrimitiveStreamDemo {
public static void main(String[] args) {
IntStream.range(1, 5).forEach(System.out::println);
}
}
Output
1 2 3 4
6. Stream vs Collection difference
- Collection stores data in memory and represents a data structure (e.g., List, Set, Map).
- Stream does not store data; it processes data from a source (like a collection) in a functional, declarative way.
To know more about Stream vs Collection refer-> Stream vs Collection
7. Java Stream: File Operation
In this section, we see how to utilize Java stream in file I/O operation.
7.1 File Read Operation
Let's understand file read operation through the given example
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class GFG {
// Method to filter strings of a given length and convert them to uppercase
private static List<String>
filterAndConvertToUpper(Stream<String> stream, int length)
{
return stream.filter(s -> s.length() == length)
.map(String::toUpperCase)
.collect(Collectors.toList());
}
public static void main(String[] args)
{
// Replace with the actual file path
String fileName = "path/to/your/file.txt";
// Step 1: Create a Stream of lines from the file
try (Stream<String> lines = Files.lines(Paths.get(fileName))) {
List<String> filteredStrings = filterAndConvertToUpper(lines, 5);
System.out.println("Filtered strings with length 5 (converted to uppercase): "+ filteredStrings);
}
catch (IOException e) {e.printStackTrace();
}
}
}
Input:
Geeks gfg geeks geeksforgeeks Coder Guys
Output:
Filtered strings with length 5 (converted to uppercase): [GEEKS, GEEKS, CODER]
7.2 File Write Operation
Let's understand file write operation through the given example
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
// Driver Class
class GFG {
// main function
public static void main(String[] args)
{
String[] words
= { "Geeks", "for", "Geeks", "Hello", "World" };
// Replace with the actual file path
String fileName = "path/to/your/file.txt";
// Step 1: Create a PrintWriter to write to the file
try (PrintWriter pw
= new PrintWriter(Files.newBufferedWriter(
Paths.get(fileName)))) {
// Step 2: Use Stream to write each word to the file
Stream.of(words).forEach(pw::println);
// Step 3: Print success message to the console
System.out.println(
"Words written to the file successfully.");
}
catch (IOException e) {
// Step 4: Handle any IO exception that occurs during the file writing process
e.printStackTrace();
}
}
}
Output:
Words written to the file successfully.
8. Java Stream: Real-life Examples

Streams are widely used in real-world applications for processing collections of data in a cleaner and faster way. Below are some practical examples:
- Filtering Employees by Salary
- Streams in a Grocery store
- Grouping Books by Author
Example: Streams in a Grocery store

Code Implementation: Here we implement a real-world example of filtering, sorting, mapping and collecting transactions using Java Streams.
import java.util.*;
import java.util.stream.Collectors;
class Transaction {
private int id;
private int value;
private String type;
public Transaction(int id, int value, String type) {
this.id = id;
this.value = value;
this.type = type;
}
public int getId() {
return id;
}
public int getValue() {
return value;
}
public String getType() {
return type;
}
}
public class StreamExample {
public static void main(String[] args) {
List<Transaction> transactions = Arrays.asList(
new Transaction(1, 100, "GROCERY"),
new Transaction(3, 80, "GROCERY"),
new Transaction(6, 120, "GROCERY"),
new Transaction(7, 40, "ELECTRONICS"),
new Transaction(10, 50, "GROCERY")
);
// Stream pipeline based on your diagram
List<Integer> transactionIds = transactions.stream()
.filter(t -> t.getType().equals("GROCERY")) // keep only groceries
.sorted(Comparator.comparing(Transaction::getValue).reversed()) // sort by value desc
.map(Transaction::getId) // map to id
.collect(Collectors.toList()); // collect as list
System.out.println(transactionIds);
}
}
Output
[6, 1, 3, 10]