Java Stream Tutorial
View more Tutorials:
Java 8 introduces a new concept called Stream. The first time you read about the Stream API, you may be confused because its name is similar to InputStream and OutputStream. But Java 8 Stream is something completely different. Stream is monad, therefore it plays an important role in bringing functional programming into Java.
Before starting this article, I recommend you to learn about functional interfaces and some common functional interfaces such as: Supplier, Consumer, Predicate. Here are my articles:
In functional programming, a monad is a structure that represents a computation that requires a sequence of steps linked together. For simplicity, see the monad example below:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1"); myList .stream() // (1) return a Stream .filter(s -> s.startsWith("c")) // (2) return a new Stream .map(String::toUpperCase) // (3) return a new Stream .sorted() // (4) return a new Stream .forEach(System.out::println); // (5)
Output:
C1 C2
- Create a Stream from a List object.
- Create a new Stream from the previous Stream and include only elements beginning with the letter "c".
- Create a new Stream from previous Stream with all elements converted to uppercase.
- Create a new Stream from the previous Stream by sorting the elements.
- Print out the elements of the last Stream.
In the previous example, steps (2) to (4) are intermediate operations because they return a Stream object. So you can call another method of Stream without having to end it with a semicolon.
A terminal operation is a method that returns void or returns a different type from Stream. In the previous example, step 5 is a terminal operation because Stream.forEach method returns void.

Here are the characteristics and advantages of Java 8 Stream:
- No storage. A Stream is not a data structure, but only a view of a data source (Which can be an array, a list or an I/O Channel,..).
- A Stream is functional in nature. Any modifications to a Stream will not change the data sources. For example, filtering a Stream will not delete any elements, but create a new Stream that includes the filtered elements.
- Lazy execution. Operations on a Stream will not be executed immediately. They will be executed only when users really need results.
- Consumable. The elements of a Stream are only visited once during the life of a Stream. Once traversed, a Stream is invalidated, just like an Iterator. You have to regenerate a new Stream if you want to traverse the Stream again.
See the illustration below to understand more how Stream works.

- Create a Stream from a collection.
- Filter colors other than red.
- Paint pink for the triangles.
- Filter shapes that are not square.
- Calculate the total area.
Employee class participates in a few examples in this article:
Employee.java
package org.o7planning.stream.ex; public class Employee { private String name; private float salary; private String gender; // "M", "F" public Employee(String name, float salary, String gender) { this.name = name; this.salary = salary; this.gender = gender; } public String getName() { return name; } public float getSalary() { return salary; } public String getGender() { return gender; } public boolean isFemale() { return "F".equals(this.getGender()); } }
Returns a Stream consisting of the elements of this Stream that match the given Predicate.
Stream<T> filter(Predicate<? super T> predicate)
Example: From a list of employees (Employee), print out a list of female employees with salaries greater than 2500.
Stream_filter_ex1.java
package org.o7planning.stream.ex; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; public class Stream_filter_ex1 { public static void main(String[] args) { Employee john = new Employee("John P.", 1500, "M"); Employee sarah = new Employee("Sarah M.", 2000, "F"); Employee charles = new Employee("Charles B.", 1700, "M"); Employee mary = new Employee("Mary T.", 5000, "F"); Employee sophia = new Employee("Sophia B.", 7000, "F"); List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia); // Employee is Female and salary > 2500 Predicate<Employee> predicate = e -> e.isFemale() && e.getSalary() > 2500; employees // .stream() // .filter(predicate) // .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary())); } }
Output:
Mary T. : 5000.0 Sophia B. : 7000.0
If a method is non-static (non-static method), has no parameters, and returns a boolean value, then its reference is considered a Predicate. (See explanation in my article about Java Predicate).
Example: Create a Predicate from a method reference:
Predicate<Employee> p = Employee::isFemale; // Same as: Predicate<Employee> p = employee -> employee.isFemale();
Stream_filter_ex2.java
package org.o7planning.stream.ex; import java.util.Arrays; import java.util.List; public class Stream_filter_ex2 { public static void main(String[] args) { Employee john = new Employee("John P.", 1500, "M"); Employee sarah = new Employee("Sarah M.", 2000, "F"); Employee charles = new Employee("Charles B.", 1700, "M"); Employee mary = new Employee("Mary T.", 5000, "F"); Employee sophia = new Employee("Sophia B.", 7000, "F"); List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia); employees // .stream() // .filter(Employee::isFemale) // .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary())); } }
Output:
Sarah M. : 2000.0 Mary T. : 5000.0 Sophia B. : 7000.0
Returns a Stream consisting of the elements of this stream, sorted according to the provided Comparator.
Stream<T> sorted(Comparator<? super T> comparator)
- TODO Link!
Example: Sorting employees in ascending salary order:
Stream_sort_ex1.java
package org.o7planning.stream.ex; import java.util.Arrays; import java.util.List; public class Stream_sort_ex1 { public static void main(String[] args) { Employee john = new Employee("John P.", 1500, "M"); Employee sarah = new Employee("Sarah M.", 2000, "F"); Employee charles = new Employee("Charles B.", 1700, "M"); Employee mary = new Employee("Mary T.", 5000, "F"); Employee sophia = new Employee("Sophia B.", 7000, "F"); List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia); employees // .stream() // .sorted ( (e1,e2) -> (int) (e1.getSalary() - e2.getSalary()) ) // .forEach(e -> System.out.println(e.getSalary() + " : " + e.getName())); } }
Output:
1500.0 : John P. 1700.0 : Charles B. 2000.0 : Sarah M. 5000.0 : Mary T. 7000.0 : Sophia B.
Example: Sort the employee by gender and salary:
Stream_sort_ex2.java
package org.o7planning.stream.ex; import java.util.Arrays; import java.util.List; public class Stream_sort_ex2 { public static void main(String[] args) { Employee john = new Employee("John P.", 1500, "M"); Employee sarah = new Employee("Sarah M.", 2000, "F"); Employee charles = new Employee("Charles B.", 1700, "M"); Employee mary = new Employee("Mary T.", 5000, "F"); Employee sophia = new Employee("Sophia B.", 7000, "F"); List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia); employees // .stream() // .sorted ( (e1,e2) -> { int v = e1.getGender().compareTo(e2.getGender()); if(v == 0) { v = (int) (e1.getSalary() - e2.getSalary()); } return v; } ) // .forEach(e -> System.out.println(e.getGender()+ " : "+ e.getSalary() + " : " + e.getName())); } }
Output:
F : 2000.0 : Sarah M. F : 5000.0 : Mary T. F : 7000.0 : Sophia B. M : 1500.0 : John P. M : 1700.0 : Charles B.
Returns a new Stream consisting of the results of applying the given Function to the elements of this Stream.
<R> Stream<R> map(Function<? super T,? extends R> mapper)
Example: Converts a list of String(s) to uppercase.
Stream_map_ex1.java
package org.o7planning.stream.ex; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Stream_map_ex1 { public static void main(String[] args) { List<String> list = Arrays.asList("a", "b", "c", "d", "e"); List<String> newList = list // .stream() // a Stream .map(s -> s.toUpperCase()) // a new Stream .collect(Collectors.toList()); // Stream => List System.out.println(list); // [a, b, c, d, e] System.out.println(newList); // [A, B, C, D, E] } }
If a method is non-static (non-static method), no parameters, and returns a value, then its reference is considered as a Function. (See more explanation in my article about Java Function).
// Create a Function from a method reference: Function<String, String> f1 = String::toUpperCase; // Same as: Function<String, String> f2 = s -> s.toUpperCase();
Stream_map_ex2.java
package org.o7planning.stream.ex; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Stream_map_ex2 { public static void main(String[] args) { List<String> list = Arrays.asList("a", "b", "c", "d", "e"); List<String> newList = list // .stream() // a Stream .map(String::toUpperCase) // a new Stream .collect(Collectors.toList()); // Stream => List System.out.println(list); // [a, b, c, d, e] System.out.println(newList); // [A, B, C, D, E] } }
Example: Double salary for every employee in a list:
Stream_map_ex3.java
package org.o7planning.stream.ex; import java.util.Arrays; import java.util.List; public class Stream_map_ex3 { public static void main(String[] args) { Employee john = new Employee("John P.", 1500, "M"); Employee sarah = new Employee("Sarah M.", 2000, "F"); Employee charles = new Employee("Charles B.", 1700, "M"); Employee mary = new Employee("Mary T.", 5000, "F"); Employee sophia = new Employee("Sophia B.", 7000, "F"); List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia); employees // .stream() // a Stream. .map((e) -> new Employee(e.getName(), e.getSalary()* 2, e.getGender())) // a new Stream. .forEach(c -> System.out.println(c.getName()+ " : " + c.getSalary())); } }
Output:
John P. : 3000.0 Sarah M. : 4000.0 Charles B. : 3400.0 Mary T. : 10000.0 Sophia B. : 14000.0
public class Stream<T> { <R> Stream<R> flatMap​(Function<? super T,​? extends Stream<? extends R>> mapper); // ..... }
Stream.flatMap method is quite an useful one. It has a lot to cover so I have split it up in a separate article:
- TODO Link!