Skip to content

A deep dive into Java Streams API with examples

Gopi Gorantala
Gopi Gorantala
3 min read
A deep dive into Java Streams API with examples

Table of Contents

Let us deep-dive into Streams API and know how they function.

Stream Phases

A Java Stream is composed of 3 main phases.

  1. Source
  2. Intermediate operations
  3. Terminal operations

Source: Data is collected from a collection. We usually call it Stream source, for example:

  • List
  • Map
  • Set
  • Array, etc.

Intermediate Operations: Every operation in the pipeline is applied to each element in a sequence. This series of processes is called Intermediate Operations, for example:

  • filter(predicate)
  • sorted()
  • distinct()
  • map(), etc.

Terminal: This means we are terminating/completing the stream operation, for example:

  • count()
  • collect(), etc.
Note: When defining a stream, we are just declaring the steps to follow a pipeline of operations, they won’t get executed until we call our terminal operation.

How does Stream API Work?

Java 8 Streams API - High-Level Design

Intermediate operations

Intermediate operations return a new stream.

They are always lazy, executing an intermediate operation such as filter() does not perform any filtering but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.

Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

Intermediate operations are further divided into stateless and stateful operations.

Stateless operations:

When processing a new element, the filter and map retain no state from previously seen elements. Each element can be processed independently of operations on other elements. Stateful operations, such as distinct and sorted, may incorporate states from previously seen elements when processing new elements.

Stateful operations

You may need to process the entire input before producing a result. For example, one cannot make any results from sorting a stream until seeing all stream elements. As a result, under parallel computation, some pipelines containing stateful intermediate operations may require multiple passes on the data or need to buffer important data. Pipelines containing exclusively stateless intermediate operations can be processed in a single pass, sequential or parallel, with minimal data buffering.

Terminal operations

Terminal operations, such as Stream.forEach(...) or IntStream.sum(...), may traverse the stream to produce a result or a side-effect.

After the terminal process, the stream pipeline is considered consumed and can no longer be used. If you need to traverse the same data source again, you must return to the data source to get a new stream.

In almost all cases, terminal operations are eager to complete their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not; these are provided as an “escape hatch” to enable arbitrary client-controlled pipeline traversals if the existing processes are insufficient for the task.

Example

In my previous article, we have seen an example of intermediate operations. Now let us see an example without intermediate processes.

Book is a POJO with a constructor, getters, and setters.

class Book {
  String title;
  String author;
  Integer year;
  Integer copiesSoldInMillions;
  Double rating;
  Double costInEuros;

  public Book(String title, String author, Integer year, Integer copiesSoldInMillions, Double rating, Double costInEuros) {
    this.title = title;
    this.author = author;
    this.year = year;
    this.copiesSoldInMillions = copiesSoldInMillions;
    this.rating = rating;
    this.costInEuros = costInEuros;
  }

  public String getAuthor() {
    return author;
  }

  public Integer getCopiesSoldInMillions() {
    return copiesSoldInMillions;
  }

  @Override
  public String toString() {
    return "Book{" +
      "title='" + title + '\'' +
      ", author='" + author + '\'' +
      ", year=" + year +
      ", copiesSoldInMillions=" + copiesSoldInMillions +
      ", rating=" + rating +
      ", costInEuros=" + costInEuros +
      '}';
  }
}

Another class BookDatabase for dummy data injection.

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

public class BookDatabase {
  public static List<Book> getAllBooks() {
    return Arrays.asList(
      new Book("Don Quixote", "Miguel de Cervantes", 1605, 500, 3.9, 9.99),
      new Book("A Tale of Two Cities", "Charles Dickens", 1859, 200, 3.9, 10.0),
      new Book("The Lord of the Rings", "J.R.R. Tolkien", 2001, 150, 4.0, 12.50),
      new Book("The Little Prince", "Antoine de Saint-Exupery", 2016, 142, 4.4, 5.0),
      new Book("The Dream of the Red Chamber", "Cao Xueqin", 1791, 100, 4.2, 10.0)
    );
  }
}

And finally out BookApplication class that does the declarative programming or immutability on each book object.

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

public class BookApplication {
  public static void main(String[] args) {
    Map<String, Integer> bookSold =
      BookDatabase.getAllBooks()
        .stream()
        .collect(Collectors.toMap(Book::getAuthor, Book::getCopiesSoldInMillions));

    System.out.println(bookSold);
  }
}

If we imagine Streams as streams of water flowing through a tank, then our job is to use each byte that gets out of the tank through the pipe with Stream API methods.

Java

Gopi Gorantala Twitter

Gopi is an Engineering Manager with over 14 years of extensive expertise in Java-based applications. He resides in Europe and specializes in designing and scaling high-performance applications.

Comments


Related Posts

Members Public

Spring Boot Hello World Tutorial with Lombok and H2 Database – Quick Start for Beginners

Learn how to create a Hello World Spring Boot application using Lombok for cleaner code and the H2 in-memory database for rapid development. This step-by-step guide includes annotations, project setup, REST API, H2 console access, and more to kickstart your Spring Boot journey.

Spring Boot Hello World Tutorial with Lombok and H2 Database – Quick Start for Beginners
Members Public

NoSuchBeanDefinitionException: The Most Common Spring Bean Error

Learn how to fix NoSuchBeanDefinitionException in Spring Boot with clear examples and best practices for dependency injection, package scanning, and bean registration.

NoSuchBeanDefinitionException for a missing bean in the ApplicationContext.
Members Public

BeanCurrentlyInCreationException: Circular Dependencies in Spring

Spring Boot’s DI can backfire with the dreaded BeanCurrentlyInCreationException, signaling an unresolvable circular dependency. Learn why cycles occur, how they breach SOLID principles, and three fixes—refactor to break loops, use @Lazy injection, and static @Bean factory methods—to restore startup.

Circular dependencies in Spring causing a BeanCurrentlyInCreationException