DEV Community

Priyank Bhardwaj
Priyank Bhardwaj

Posted on

Intermediate Operations in Java Streams — A Complete Guide for Developers

In the previous article, we explored how to create streams in Java using:

  • Collections
  • Arrays
  • Stream.of()
  • Stream.generate()

Now that we know how to create streams, it’s time to understand the real power of Java Streams — Intermediate Operations.

Intermediate operations allow us to transform, filter, and manipulate data inside a stream before producing the final result.

Let’s dive deep into intermediate operations in Java Streams with practical examples.


What are Intermediate Operations?

Intermediate operations are operations that:

  • Transform a stream into another stream
  • Do NOT produce final results
  • Are lazy (they run only when a terminal operation is executed)
  • Can be chained together

Example

List<Integer> list = List.of(1, 2, 3, 4, 5);

list.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * 2)
    .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Here:

  • filter() → intermediate
  • map() → intermediate
  • forEach() → terminal operation

Without a terminal operation, intermediate operations won’t execute.


Why Intermediate Operations Are Lazy?

Streams use lazy evaluation to improve performance.

This means:

  • No processing happens until a terminal operation is called
  • Operations are applied only when needed
  • Helps optimize performance

Example

Stream<Integer> stream = Stream.of(1,2,3,4,5)
        .filter(n -> {
            System.out.println("Filtering: " + n);
            return n % 2 == 0;
        });

System.out.println("No terminal operation yet!");
Enter fullscreen mode Exit fullscreen mode

Nothing prints because no terminal operation is present.

Add terminal operation:

stream.forEach(System.out::println);

Now filtering happens.


Common Intermediate Operations in Java Streams

Let’s explore the most important ones.

1. filter()

Used to filter elements based on condition.

Syntax
stream.filter(condition)

Example

List<String> names = List.of("Ram", "Shyam", "Amit", "Ankit");

names.stream()
     .filter(name -> name.startsWith("A"))
     .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

Amit
Ankit
Enter fullscreen mode Exit fullscreen mode

2. map()

Transforms each element into another form.

Syntax
stream.map(function)

Example

List<Integer> numbers = List.of(1,2,3,4);

numbers.stream()
       .map(n -> n * n)
       .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

1
4
9
16
Enter fullscreen mode Exit fullscreen mode

Used for:

  • Transforming data
  • Converting objects
  • Extracting fields

3. flatMap()

Used to flatten nested structures.

Example: List of lists

List<List<Integer>> list = List.of(
        List.of(1,2),
        List.of(3,4),
        List.of(5,6)
);

list.stream()
    .flatMap(l -> l.stream())
    .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

1 2 3 4 5 6

flatMap() converts Stream → Stream

4. distinct()

Removes duplicate elements.

Example

List<Integer> numbers = List.of(1,2,2,3,4,4,5);

numbers.stream()
       .distinct()
       .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

1 2 3 4 5

5. sorted()

Sorts stream elements.

Natural sorting

List<Integer> list = List.of(5,1,4,2,3);

list.stream()
    .sorted()
    .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

1 
2 
3 
4 
5 
Enter fullscreen mode Exit fullscreen mode

Custom sorting

List<String> names = List.of("Ram","Amit","Shyam");

names.stream()
     .sorted((a,b) -> b.compareTo(a))
     .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

Amit
Ram
Shyam
Enter fullscreen mode Exit fullscreen mode

Note: The sorted() function first sorts the entire list and only after it finishes its operation, forEach() prints the list.

6. limit()

Limits number of elements.

Stream.iterate(1, n -> n + 1)
      .limit(5)
      .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

1 
2 
3 
4 
5
Enter fullscreen mode Exit fullscreen mode

7. skip()

Skips first N elements.

List<Integer> list = List.of(1,2,3,4,5,6);

list.stream()
    .skip(3)
    .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

4 
5 
6
Enter fullscreen mode Exit fullscreen mode

8. peek()

Used for debugging (not recommended for logic).

List<Integer> list = List.of(1,2,3);

list.stream()
    .peek(n -> System.out.println("Processing: " + n))
    .map(n -> n * 2)
    .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

Processing: 1
2
Processing: 2
4
Processing: 3
6
Enter fullscreen mode Exit fullscreen mode

Used mainly for:

  • Debugging
  • Logging stream flow

Chaining Multiple Intermediate Operations

This is where streams make coding simpler and easier to comprehend.

List<String> names = List.of("Ram", "Amit", "Shyam", "Ankit", "Rahul");

names.stream()
     .filter(n -> n.startsWith("A"))
     .map(String::toUpperCase)
     .sorted()
     .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Output

AMIT
ANKIT
Enter fullscreen mode Exit fullscreen mode

Key Characteristics of Intermediate Operations

Feature Description
Lazy Executes only after terminal operation
Returns Stream Always returns another stream
Chainable Multiple operations can be chained
Non-destructive Original data remains unchanged

Real-World Example (Employee Processing)

Problem statement: Given a list of employees, print the names of all the employees with salary greater than 50000 in descending order of their salaries i.e employee with highest salary should be printed first.

class Employee {
    String name;
    int salary;

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

    public String toString() {
        return name + " : " + salary;
    }
}

public class Main {
    public static void main(String[] args) {

        List<Employee> employees = List.of(
                new Employee("Ram", 50000),
                new Employee("Amit", 70000),
                new Employee("Shyam", 40000),
                new Employee("Ankit", 90000)
        );

        employees.stream()
                .filter(e -> e.salary > 50000)
                .sorted((a,b) -> b.salary - a.salary)
                .map(e -> e.name)
                .forEach(System.out::println);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Ankit
Amit
Enter fullscreen mode Exit fullscreen mode

Conclusion

Intermediate operations are the backbone of Java Streams.

They help you:

  • Write clean functional code
  • Process data efficiently
  • Avoid complex loops
  • Improve readability and performance

Once you master intermediate operations like filter(), map(), flatMap(), and sorted(), you unlock the true power of Java Streams.


What’s Next?

In the next article, we’ll cover:

Terminal Operations in Java Streams

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

This is Part 4 of the Java Streams Series.

If you find it insightful, please share your feedback. Also let me know if you have used builder pattern in your projects.

Next Up: Terminal Operations!

Top comments (0)