Advanced Java August 19 ,2025

Java Stream API – 

1. Introduction to Java Stream API

The Stream API was introduced in Java 8 to process collections of data in a functional programming style.
It allows performing bulk operations (like filtering, mapping, reducing, etc.) on data with ease.

Key Points:

  • A Stream represents a sequence of elements.
  • It does not store data; it provides a pipeline to process data.
  • Stream operations are functional (use lambda expressions, functional interfaces).
  • Stream is different from java.io.Stream (input/output streams).

2. Why Use Streams?

Before Java 8, to filter or transform data, developers had to use loops and iterators, making code verbose and less readable.

With Stream API:

  • Less boilerplate code
  • Readability (code looks closer to SQL queries)
  • Parallel processing (multi-core CPUs utilized)
  • Declarative style (focus on what to do, not how to do)

3. Stream API Workflow

A Stream operation generally has 3 stages:

  1. Source – Where data comes from
    Examples: Collection, Arrays, I/O channels, or Stream.of()
  2. Intermediate Operations – Transform the stream (return a new stream)
    Examples: filter(), map(), sorted()
  3. Terminal Operations – Produce a result (end the stream)
    Examples: collect(), forEach(), reduce()

You’re right to check that — in my earlier draft I explained what Streams are, their operations, and examples, but I did not explicitly include a “How to Create a Java Stream?” section.

That’s an important part, because before performing operations we must know how to instantiate a Stream. Let me add that section for you in a detailed theory + practical style.

4. How to Create a Java Stream?

Streams in Java can be created in multiple ways depending on the data source. Here are the main approaches:

1. From Collections

Every collection class (List, Set, etc.) in Java has a built-in method stream() that creates a sequential stream, and parallelStream() that creates a parallel stream.

Example:

import java.util.*;
import java.util.stream.*;

public class CollectionStreamExample {
    public static void main(String[] args) {
        List names = Arrays.asList("Rahul", "Amit", "Neha", "Priya");

        // Sequential stream
        Stream sequentialStream = names.stream();
        sequentialStream.forEach(System.out::println);

        // Parallel stream
        Stream parallelStream = names.parallelStream();
        parallelStream.forEach(System.out::println);
    }
}

2. Using Arrays

The Arrays.stream() method or Stream.of() can be used to create streams from arrays.

Example:

import java.util.stream.*;

public class ArrayStreamExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};

        // Stream from array
        IntStream intStream = Arrays.stream(numbers);
        intStream.forEach(System.out::println);

        // Another way
        Stream numberStream = Stream.of(10, 20, 30, 40);
        numberStream.forEach(System.out::println);
    }
}

3. Using Stream.of()

Directly create a stream from given values.

Example:

import java.util.stream.*;

public class StreamOfExample {
    public static void main(String[] args) {
        Stream stream = Stream.of("Java", "Python", "C++", "Go");
        stream.forEach(System.out::println);
    }
}

4. Using Stream.generate()

Used to create infinite streams by supplying a Supplier function.

Example:

import java.util.stream.*;

public class StreamGenerateExample {
    public static void main(String[] args) {
        // Generates random numbers
        Stream randomNumbers = Stream.generate(Math::random);

        // Limiting to 5 elements
        randomNumbers.limit(5).forEach(System.out::println);
    }
}

5. Using Stream.iterate()

Also creates infinite streams but by applying a function iteratively.

Example:

import java.util.stream.*;

public class StreamIterateExample {
    public static void main(String[] args) {
        // Start with 1, keep adding 2
        Stream oddNumbers = Stream.iterate(1, n -> n + 2);

        // Limiting to 5 elements
        oddNumbers.limit(5).forEach(System.out::println);
    }
}

6. Using Files.lines() (Stream from a File)

The Files.lines() method can create a stream of lines from a text file.

Example:

import java.nio.file.*;
import java.io.IOException;
import java.util.stream.*;

public class FileStreamExample {
    public static void main(String[] args) throws IOException {
        Stream lines = Files.lines(Paths.get("example.txt"));

        lines.forEach(System.out::println);
        lines.close();
    }
}

 

5. Intermediate Operations (Transformations)

Intermediate operations are operations performed on a Stream that transform it into another stream.
They are lazy, meaning they don’t execute immediately. They only get executed when a terminal operation (like forEach, collect, reduce) is called.
Multiple intermediate operations can be chained together, forming a pipeline.

Intermediate operations return a new stream, allowing method chaining. They are lazy, meaning they don’t run immediately — execution happens only when a terminal operation (like forEach(), collect()) is called.

(a) filter()

  • Used to select elements that match a given condition.
  • Returns a stream containing only those elements that satisfy the predicate.
  • Often used for conditional processing.

Key Point: Doesn’t modify elements, only reduces the stream based on condition.

List nums = Arrays.asList(10, 20, 30, 40);
nums.stream()
    .filter(n -> n > 20)  
    .forEach(System.out::println);  // Output: 30, 40

(b) map()

  • Used to transform each element into another form.
  • Accepts a function and applies it to every element.
  • Most commonly used for data conversion (e.g., lowercase → uppercase, number → square).

Key Point: The number of elements usually remains the same, but their type or value changes.

List names = Arrays.asList("john", "doe");
names.stream()
     .map(String::toUpperCase)  
     .forEach(System.out::println);  // Output: JOHN, DOE

(c) flatMap()

  • Used when elements themselves are collections (or streams).
  • Converts each sub-collection into a stream, then flattens them into a single stream.
  • Helps avoid nested collections.

Key Point: map() → returns stream of collections, flatMap() → merges them into one stream.

List> list = Arrays.asList(
    Arrays.asList("A", "B"),
    Arrays.asList("C", "D")
);

list.stream()
    .flatMap(Collection::stream)  
    .forEach(System.out::println);  // Output: A, B, C, D

(d) distinct()

  • Removes duplicate elements.
  • Uses equals() method for comparison.
  • Ensures uniqueness in the stream.
Stream.of(1, 2, 2, 3, 3, 4)
      .distinct()
      .forEach(System.out::println);  // Output: 1, 2, 3, 4

(e) sorted()

  • Sorts stream elements.
  • By default, sorts in natural order.
  • Can also accept a custom Comparator.
Stream.of(5, 1, 3, 2)
      .sorted()
      .forEach(System.out::println);  // Output: 1, 2, 3, 5

Custom Comparator:

Stream.of("apple", "banana", "cherry")
      .sorted(Comparator.reverseOrder())
      .forEach(System.out::println);  // Output: cherry, banana, apple

(f) limit() & skip()

  • limit(n): restricts stream to first n elements.
  • skip(n): ignores the first n elements and processes the rest.
  • Useful in pagination or sampling.
Stream.of(10, 20, 30, 40, 50)
      .limit(3)
      .forEach(System.out::println);  // Output: 10, 20, 30
Stream.of(10, 20, 30, 40, 50)
      .skip(2)
      .forEach(System.out::println);  // Output: 30, 40, 50

6. Terminal Operations (Results)

Terminal operations are the final operations on a stream. They produce a result (non-stream value like int, List, Optional, etc.) or a side-effect (like printing).
After a terminal operation, the stream is consumed and cannot be reused.

(a) forEach()

  • Performs an action for each element of the stream.
  • Usually used for printing or applying side effects.
Stream.of("A", "B", "C")
      .forEach(System.out::println);  
// Output: A B C

(b) toArray()

  • Collects elements into an array.
String[] arr = Stream.of("X", "Y", "Z")
                     .toArray(String[]::new);
System.out.println(Arrays.toString(arr));  
// Output: [X, Y, Z]

(c) reduce()

  • Reduces elements to a single value (aggregation).
int sum = Stream.of(1, 2, 3, 4)
                .reduce(0, (a, b) -> a + b);
System.out.println(sum);  // 10

(d) collect()

  • Collects results into a collection or map.
  • Most powerful terminal operation.
List list = Stream.of("apple", "banana", "apple")
                          .collect(Collectors.toList());
System.out.println(list);  
// Output: [apple, banana, apple]
Set set = Stream.of("apple", "banana", "apple")
                        .collect(Collectors.toSet());
System.out.println(set);  
// Output: [apple, banana]

(e) min() and max()

  • Finds the minimum or maximum element based on a comparator.
int min = Stream.of(5, 9, 1, 7)
                .min(Integer::compare)
                .get();
System.out.println(min);  // 1
int max = Stream.of(5, 9, 1, 7)
                .max(Integer::compare)
                .get();
System.out.println(max);  // 9

(f) count()

  • Returns the number of elements in the stream.
long count = Stream.of(10, 20, 30, 40)
                   .count();
System.out.println(count);  // 4

(g) anyMatch(), allMatch(), noneMatch()

  • Used for checking conditions.
boolean any = Stream.of(1, 2, 3, 4)
                    .anyMatch(n -> n > 3);
System.out.println(any);  // true

boolean all = Stream.of(1, 2, 3, 4)
                    .allMatch(n -> n > 0);
System.out.println(all);  // true

boolean none = Stream.of(1, 2, 3, 4)
                     .noneMatch(n -> n < 0);
System.out.println(none);  // true

(h) findFirst() and findAny()

  • Find first or any element in the stream (returns Optional).
Optional first = Stream.of(10, 20, 30)
                                .findFirst();
System.out.println(first.get());  // 10

Optional any = Stream.of(10, 20, 30)
                              .findAny();
System.out.println(any.get());  // could be 10, 20, or 30

 

7. Collectors Utility Class

The Collectors class (in java.util.stream.Collectors) is a utility class that provides a variety of static methods to perform reduction operations on stream elements and return them in a desired collection or result type. It is part of the java.util.stream package and works hand-in-hand with the collect() terminal operation.

Collectors are essential when you want to transform a stream into a data structure (like a List, Set, or Map), aggregate values (like counting or averaging), or perform grouping and partitioning.

7.1. Key Features of Collectors

  1. Immutable Results – The result of a collector operation is often an immutable collection or final value.
  2. Flexible Data Reduction – Provides wide support for reduction into collections, strings, maps, numeric aggregates, etc.
  3. Custom Collectors – Developers can build custom collectors using Collector.of(...).
  4. Parallel Friendly – Collectors are designed to work in both sequential and parallel stream pipelines.

7.2. Commonly Used Collectors Methods

a) toList(), toSet(), toMap()

  • Collect elements into a List, Set, or Map.
  • toList() → Collects into a List (e.g., ArrayList).
  • toSet() → Collects into a Set (eliminates duplicates).
  • toMap(keyMapper, valueMapper) → Collects into a Map with custom key/value extraction functions.

b) joining()

  • Concatenates the stream of CharSequence elements into a single String.
  • Overloaded methods allow specifying a delimiter, prefix, and suffix.

Example:

List names = Arrays.asList("John", "Jane", "Jack");
String result = names.stream()
                     .collect(Collectors.joining(", ", "[", "]"));
System.out.println(result); 
// Output: [John, Jane, Jack]

c) groupingBy()

  • Groups stream elements based on a classification function and returns a Map> by default.
  • Overloads allow grouping into different collection types and applying downstream collectors (e.g., counting, summing).

Example:

Map> grouped = 
    names.stream().collect(Collectors.groupingBy(name -> name.charAt(0)));

d) partitioningBy()

  • Partitions stream elements into two groups (true/false) based on a given predicate.
  • Returns a Map>.
  • Useful for binary classification.

Example:

Map> partitioned = 
    names.stream().collect(Collectors.partitioningBy(name -> name.length() > 3));

7.3. Advanced Collectors

  1. counting() → Counts elements.
  2. summingInt(), summingDouble(), summingLong() → Summation of numerical values.
  3. averagingInt(), averagingDouble(), averagingLong() → Computes average.
  4. maxBy(comparator), minBy(comparator) → Finds max/min element.
  5. collectingAndThen(collector, finisher) → Wraps another collector and applies a finishing function.
  6. mapping(mapper, downstream) → Applies a mapping function before reduction.

7.4. Practical Importance

  • Data Transformation → Transform streams into collections/maps for further processing.
  • Aggregation → Easily calculate sum, average, count, min, and max.
  • Grouping and Partitioning → Simplifies classification and categorization of data.
  • Readable and Declarative → Makes code concise compared to imperative looping.

 

8. Parallel Streams

The Stream API not only provides sequential stream processing but also supports parallel execution to take advantage of multi-core processors. A parallel stream divides the given data source into multiple chunks, processes them in parallel across different threads, and then combines the results.

Key Concepts of Parallel Streams:

  1. Creation of Parallel Stream

    • You can obtain a parallel stream in two ways:
      • By calling .parallelStream() on a Collection.
      • By calling .parallel() on an existing sequential stream.
    List numbers = Arrays.asList(1,2,3,4,5,6,7,8);
    numbers.parallelStream().forEach(System.out::println);
    

    Here, elements may be printed in any order because execution happens concurrently.

  2. Work Splitting (Fork/Join Framework)
    • Internally, Java uses the Fork/Join framework (introduced in Java 7) to split the data source into sub-tasks.
    • Each sub-task is processed by a different worker thread in the common ForkJoinPool.
    • Once all sub-tasks finish execution, results are merged.
  3. Performance Considerations
    • Large Datasets → Parallel streams perform better when dealing with large collections.
    • Small Datasets → Parallel overhead (splitting + thread management) can make it slower than sequential streams.
    • CPU-Intensive Operations → Best suited when tasks are computationally heavy.
    • I/O-Bound Operations → Not beneficial, as threads may block.
  4. Ordering Issues

    • Parallel streams do not guarantee order unless you explicitly use forEachOrdered().
    numbers.parallelStream()
           .forEachOrdered(System.out::println); // Maintains order
    
  5. Thread-Safety Concerns
    • Since execution happens concurrently, shared mutable state must be avoided.
    • For example, updating a global ArrayList inside a parallel stream can cause race conditions.
    • Always use collectors or thread-safe data structures (ConcurrentHashMap, CopyOnWriteArrayList) for parallel operations.
  6. When to Use Parallel Streams
    ✅ Suitable for:

    • Large data collections
    • CPU-bound operations
    • Stateless and non-blocking tasks

    ❌ Avoid in:

    • Small collections (overhead > benefit)
    • I/O heavy tasks (blocking threads)
    • Tasks requiring strict ordering
  7. Example: Summing with Parallel Streams
List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

int sum = list.parallelStream()
              .mapToInt(Integer::intValue)
              .sum();

System.out.println("Sum: " + sum); // Output: 55

 

9. Practical Use Case – Employee Filtering

class Employee {
    String name;
    double salary;

    Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
}

public class EmployeeStream {
    public static void main(String[] args) {
        List employees = Arrays.asList(
            new Employee("John", 50000),
            new Employee("Jane", 60000),
            new Employee("Jack", 40000)
        );

        // Find employees with salary > 45,000
        employees.stream()
                 .filter(e -> e.salary > 45000)
                 .map(e -> e.name)
                 .forEach(System.out::println);  // John, Jane
    }
}

10. Key Differences – Stream vs Collection

FeatureCollectionStream
StorageStores elementsDoes not store elements
IterationExternal (loops, iterators)Internal (stream pipeline)
MutabilityMutableImmutable
EvaluationEagerLazy
ProcessingSequentialSequential or Parallel

11. Advantages of Stream API

  • Clean & declarative code
  • Supports functional programming
  • Lazy evaluation (efficient)
  • Parallel execution support
  • Rich set of built-in operations

 Key Takeaways from Java Stream API

  1. Streams are not Collections – They provide a pipeline for data processing, not a data structure for storage.
  2. Functional Programming in Java – Streams bring declarative, functional-style operations (map, filter, reduce) to collections.
  3. Lazy Evaluation – Intermediate operations (like map, filter) are lazy and executed only when a terminal operation is invoked.
  4. Powerful Intermediate Operations – Enable transformation, filtering, mapping, sorting, distinct values, and more.
  5. Terminal Operations Produce Results – Such as aggregation (reduce, count), collection (collect), or side-effects (forEach).
  6. Collectors Utility Class – Provides advanced collection capabilities like groupingBy, partitioningBy, joining, and mapping results into lists, sets, or maps.
  7. Parallel Streams – Allow multi-threaded execution but should be used cautiously due to non-deterministic ordering and potential overhead.
  8. Readable & Concise Code – Complex data processing tasks can be written in a clean and maintainable way using streams.
  9. Performance Considerations – While streams improve readability, they don’t always outperform traditional loops; careful use is required in performance-critical applications.
  10. Modern Java Development – Mastery of the Stream API is essential for writing efficient, elegant, and modern Java code.

     

Next Blog-  JDBC (Java Database Connectivity)

Sanjiv
0

You must logged in to post comments.

Related Blogs

Generics P...
Advanced Java August 08 ,2025

Generics Part- 2

Collection...
Advanced Java July 07 ,2025

Collections Framewor...

Mastering...
Advanced Java August 08 ,2025

Mastering Java Multi...

Annotation...
Advanced Java August 08 ,2025

Annotations

Java Memor...
Advanced Java August 08 ,2025

Java Memory Manageme...

Java Lambd...
Advanced Java August 08 ,2025

Java Lambda Expressi...

Java Funct...
Advanced Java August 08 ,2025

Java Functional Inte...

JDBC (Java...
Advanced Java August 08 ,2025

JDBC (Java Database...

JDBC (Java...
Advanced Java September 09 ,2025

JDBC (Java Database...

Annotation...
Advanced Java August 08 ,2025

Annotations

Generics
Advanced Java August 08 ,2025

Generics

Java I/O (...
Advanced Java August 08 ,2025

Java I/O (NIO)

Introducti...
Advanced Java September 09 ,2025

Introduction to Desi...

Design Pat...
Advanced Java September 09 ,2025

Design Patterns in J...

Other Prin...
Advanced Java September 09 ,2025

Other Principles Beh...

Creational...
Advanced Java September 09 ,2025

Creational Design Pa...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

Creational...
Advanced Java September 09 ,2025

Creational Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Builder De...
Advanced Java September 09 ,2025

Builder Design Patte...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Design Pat...
Advanced Java September 09 ,2025

Design Patterns in J...

Chain of R...
Advanced Java September 09 ,2025

Chain of Responsibil...

Command De...
Advanced Java September 09 ,2025

Command Design Patte...

Interprete...
Advanced Java September 09 ,2025

Interpreter Design P...

Iterator D...
Advanced Java September 09 ,2025

Iterator Design Patt...

Mediator D...
Advanced Java September 09 ,2025

Mediator Design Patt...

Memento De...
Advanced Java September 09 ,2025

Memento Design Patte...

Observer D...
Advanced Java September 09 ,2025

Observer Design Patt...

State Desi...
Advanced Java September 09 ,2025

State Design Pattern...

Strategy D...
Advanced Java September 09 ,2025

Strategy Design Patt...

Template M...
Advanced Java September 09 ,2025

Template Method Desi...

Visitor De...
Advanced Java September 09 ,2025

Visitor Design Patte...

Prototype...
Advanced Java September 09 ,2025

Prototype Design Pat...

Java 8+ Fe...
Advanced Java October 10 ,2025

Java 8+ Features

SOLID Prin...
Advanced Java October 10 ,2025

SOLID Principles in...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

How Iterat...
Advanced Java October 10 ,2025

How Iterators Work i...

How Concur...
Advanced Java October 10 ,2025

How ConcurrentHashMa...

Comparable...
Advanced Java October 10 ,2025

Comparable vs Compar...

Semaphore...
Advanced Java October 10 ,2025

Semaphore in Java

Get In Touch

G06, Kristal Olivine Bellandur near Bangalore Central Mall, Bangalore Karnataka, 560103

+91-8076082435

techiefreak87@gmail.com