Stream API: Hero Without a Cape



By Imran.shaikh

On Mar 21, 2020 at 01:43 pm



Hey Tea Lovers! In this post, we will be talking about the Stream API which was added to Java 8 and it has changed the way we do programs in java and how! It added the neatness as well as made the program more readable. It helps you do functional programming in Java. Without any further ado, let us know more about this jewel.


What is Stream API

The Stream API, included in Java 8, is utilized for processing collections of Objects. It is the flow of Objets on which various methods get applied in the pipeline to have the result. Simply put, it flows through the given Collection<Object> and applies the different methods on the Object to aggregate them into the desired result without affecting the original Object.


What Stream provides

Stream doesn’t store anything instead, it operates on the given CollectionArray or I/O (yes you can use it on I/O) Without changing the original data, it applies the methods to the data. Lazy evaluation helps to add multiple intermediate operations without running the full stream. Evaluated only after we add terminal operation. Adds Neatness and make codes cleaner. Produce a more comprehensive code. And many more.


Things to Know Before You Go

Stream allows you to do functional programming in Java. Which means you should be comfortable with Functional Interfaces and lambda. You just need basic Functional Interfaces to get started, which can be found in the post Functional Interface. We have discussed lambdas as well. If you know these well, nobody will stop you from understanding the Stream API. You can find the code on github here or the full project here. And yes fill your cup of tea, start sipping and learn in your tea break.


How to Stream

We can do the trick in multiple ways. Such magic tricks are,

  • Collection’s stream() and parallelStream() methods
  • Via[])
  • Stream’s static factory methods such  asp
    • Stream.of(Object[])IntStream.range(int, int) or 
    • Stream.iterate(Object, UnaryOperator)
  • Streaming the line of a file by BufferedReader.lines()
  • Streams of random numbers can be obtained from Random.ints()

1st and 2nd are what we use most often. For our discussion, we will be sticking to the Collection#steam() method. For Example,

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
/* start the stream */
/*multiplying each number by 2 */
.map(n -> n * 2)
/*taking forward only even numbers */
.filter(n -> n % 2 == 0)
/*printing each even number */
.forEach(n -> System.out.println(n));


Stream Pipeline Operations

We can perform two operations on a Stream pipeline

  • Intermediate Operation
  • Terminal Operation

Intermediate Operation transforms or filters the object in the preceding pipeline. These methods are nothing but the Functional Interfaces we discussed in our [previous post]. You should get familiar with them to easily understand this operation without any trouble. Stream won’t execute intermediate operations unless Terminal Operation is being called. It is an indication for the Stream to start the pipeline. Once we call the terminal operation pipeline ends and it returns the desired result. After the terminal operation, we cant use intermediate operation since the pipeline has ended. It can only be called once at the end of the pipeline. However, you can again start the stream if the terminal operation returns a collection. Which I highly oppose doing. You can pretty much do all the things inside a single stream pipeline only.


Intermediate Operations

In the Stream pipeline, we can have multiple intermediate operations that transform or filter the data which will be flowing towards the next pipe without affecting the actual data on the Collection. These operations return the Stream of the desired result instead of the actual result through which we can add more intermediate operation without breaking the Stream pipeline. We will be talking about the map and filter in this post and others in another part. map and filter are the ones you will be needing most of the time.

These methods are applied to one by one to the elements in the pipeline and not all at once.



Think of it as a transforming function, which transforms the given object to another. It accepts a Function as a parameter, which takes the element in the stream as input, performs some operation we tell it to do and returns the Stream of resulting object. Learn about Function here If we look at our previous example, it is multiplying each number by 2. We can have another map which can add 2 to the number and then another one to convert it to another object. and then another one and so on, you get the idea.
/*1. multiplying each number by 2 */
.map(n -> n * 2)
/*2. Converting to string. */
.map(n -> " No : " + n)
/*3. Now you will be working on the stream of String */
.map(string -> string + ". Another String")
/*4. we add another string to the previous, print the end result */

In the above example look how we transformed a list of Integer to the String. Lets breakdown it.

  1. Multiplying each number by 2. So 1 becomes 2, 2 becomes 4 and so on.
  2. We have attached the number to a string resulting in a String Object.
  3. The previous map made the Stream<Integer> to Stream<String> due to the return type of string.
  4. We again attached another string to perform some operation.
  5. Finally printing the results. Which looks like,


Output: No : 2. Another String No : 4. Another String No : 6. Another String No : 8. Another String No : 10. Another String No : 12. Another String No : 14. Another String No : 16. Another String No : 18. Another String No : 20. Another String



The filter, as the name suggests, filter outs the elements before it reaches the next pipe. Simply put, if the element satisfies the condition it allows it to pass through otherwise it won’t allow for the next operation. It takes a Predicate as a parameter. Know about Predicate here.

long count =
/* 1. Need only even numbers */
.filter(n -> n % 2 == 0)
/* 2. Checking if the Number is greater than 5 */
.filter(n -> n > 5)
/* 3. multiplying the number by 5
just to show how we can use multiple
Intermediate operation together */
.map(n -> n * 5)
/* 4. Any number less than 50 */
.filter(n -> n < 50)
/* Counting how much elements
survived teh pipeline */
System.out.println("Total " + count + " numbers survived the storm");

So many filters. Ok, let break this down, shall we?

  1. Take only even numbers ahead.
  2. Forward only those numbers which are greater than 5
  3. Multiply the number by 5. Again, just to show you can have multiple Intermediate operations in a single Stream pipeline.
  4. Allow numbers that are less than 50.
  5. Finally, count (Terminal Operation) how many survived after this much filtering.



Total 2 numbers survived the storm


Terminal Operation

Till now we have seen the operations which perform something in the pipe and return the stream. In the end, we will be needing a final result, don’t we? That’s where Terminal Operation comes. It not only gives the desired result but also starts the Stream. As I said, Stream is lazy, it won’t do anything, no matter how many intermediate operations we add to the pipeline, unless it sees its end, the terminal operation.

You cant use the same stream after it was closed by a terminal operation. Otherwise, it will throw IllegalStateException: stream has already been operated upon or closed. You have to start a new stream

The terminal operations we have used in the above examples are forEach and count. Let us examine them and a few others.



It takes input, but returns no output.It Takes Consumer as a parameter, click for more details on Consumer. Use this if you want to do something without returning anything. We have used it for printing the numbers.
/* square of each number */
.map(n -> n * n )
.forEach(n -> System.out.print(" "+ n));
1 4 9 16 25 36 49 64 81 100 */



As the name suggests, it is used for counting the elements reached to the end. Like in filter example, counting the number who satisfied all the filter’s conditions.
/* even numbers less than 5s */
.filter(n -> n < 5 && n %2 == 0)
.count(); /*returns 2 */



I use this more often than any other terminal operation. It allows you to create the Collection from the elements of the stream after processing them. These Collection can be ListSet or Map. The Collection includes elements that passed through the last pipe.

List<Integer> processedList =
/* multiplying by 5 */
.map(n -> n * 5)
/* numbers less than 30 */
.filter(n -> n < 30)
/* add +2 */
.map(n -> n + 2)
/* give the list of processed numbers */
System.out.println("processed List is " + processedList);

In the pipeline we have multiplied the number by 5, then filtered numbers less than 30 and added 2. And after all the elements are done process, we collect them into the list.

processed List is [7, 12, 17, 22, 27]


The Conclusion

We have studied what is Stream, how to obtain it, how to operate it, use it. This post is kind of like an introductory post for the Stream API. There are so many diamonds in the mine, which we will be mining in the next post. I highly recommend you to check out the Functional Interface post for better understanding the Stream API operation.You can find the code on github here or the full project here.


#Hakuna Matata 

   JavaJava8Functional ProgrammingStream API

About Contributer

Questions / Comments


Be the first to comment on this post.