Introduction to Akka Streams

Reading Time: 4 minutes

Hey folks, let us understand the basics of akka streams. I hope you have a basic understanding of Akka Actor.

What is Akka Streams

Akka Streams is a library to process and transfer a sequence of elements. It is built on top of Akka Actors to make the ingestion and processing of streams easy. As it is build on top of Akka Actors, it provide a higher-level abstraction over Akka’s existing actor model. Akka streams consist of 3 major components in it – Source, Flow, Sink – and any non-cyclical stream consist of at least 2 components Source, Sink and any number of Flow element. Here we can say Source and Sink are the special cases of Flow. Here Flow sits in between the Source and Sink as they are the Transformations applied on the Source data.

Features of Akka Streams

  • Akka-streams is very useful for fast streaming data.
  • It avoids lots of boilerplate code required to manage the actor.
  • It is best suited for big data-based applications.
  • As it is built on Akka Toolkit, we will get all Akka Toolkit benefits, such as Reactiveness, Distributed, Location Transparency, Clustering, Remoting etc.
  • It provides reusability, which means once we design data flow graph, we can reuse it any number of times.

Terminology in Akka-Streams

1. Source :

This is the entry point to your stream. There must be at least one source in every stream. It takes two type parameters. The first one represents the type of data it emits and the second one is the type of the auxiliary value it can produce when run. If we don’t produce any we use the NotUsed type provided by Akka. It has only one output point. Source can be considered as publisher .

val source : Source[Int, NotUsed] = (1 to 1000)

2. Sink :

This is the exit point of your stream. There must be at least one sink in every stream.The Sink is the last element of our stream. Basically it’s a subscriber of the data sent/processed by a source. Usually it outputs its input to some system IO.It is the endpoint of a stream and therefore consumes data. A Sink has a single input channel and no output channel. Sinks are especially needed when we want to specify the behaviour of the data collector in a reusable way and without evaluating the stream. Sink can be considered as subscriber. 

val sink: Sink[Int, Future[Done]] = Sink.foreach(println)

3. Flow :

The flow is a processing step within the stream. It combines one incoming channel and one outgoing channel as well as some transformation of the messages passing through it. If a flow is connected to a source a new source is the result. Likewise, a flow connected to a sink creates a new sink. And a flow connected with both a source and a sink results in a RunnableFlow. Therefore, they sit between the input and the output channel but by themselves do not correspond to one of the flavors as long as they are not connected to either a Source or a Sink. Here Flow sits in between the Source and Sink as they are the Transformations applied on the Source data.

val flow: Flow[Int, Int, NotUsed] = Flow[Int].map(_ + 1)

4. RunnableGraph :

A Flow that has both ends attached to a Source and Sink respectively is ready to be run() and is called a RunnableGraph. Even after constructing the RunnableGraph by connecting all the source, sink and different operators, no data will flow through it. This is where Materialization comes into action!

5. Materializer :

Flows and graphs in Akka Streamsare like preparing a blueprint/execution plan. Stream materialization is the process of taking a stream description and allocating all the necessary resources it needs in order to run. This means starting up Actors which power the processing, and much more under the hood depending on what the stream needs.After running (materializing) the RunnableGraph we get back the materialized value of specified type. Every stream operator can produce a materialized value. Akka has .toMat to indicate that we want to transform the materialized value of the source and sink.

Now we have an idea of what is akka streams, how they work etc. So let’s see Akka Streams in action.

Akka Streams in action

import akka.{Done, NotUsed}
import{Flow, Sink, Source}
import scala.concurrent.Future

object Application extends App {

  implicit val system: ActorSystem = ActorSystem("akka-streams-demo")
  implicit val materializer: ActorMaterializer = ActorMaterializer()

  val numberSource: Source[Int, NotUsed] = Source(1 to 100)

  val sink: Sink[Int, Future[Done]] = Sink.foreach(println)

  val flow: Flow[Int, Int, NotUsed] = Flow[Int].filter(number => isPrime(number))


  private def isPrime(number: Int): Boolean = {
    if (number <= 1) false
    else if (number == 2) true
    else !(2 until number).exists(i => number % i == 0)

Output :

  • We have created an ActorSystem and an ActorMaterializer instances in scope to materialize the graph.
  • Now Create a Source with range 1 to 100
  • Flow that filters only prime number
  • Create a sink that will print out its input to the console using println.
  • Finally connect numberSource via flow to sink and running it by using run().


In this article, we were looking at the akka-stream library. What is akka-stream, Features of akka-streams and a very basic exmaple to see akka-streams in action. We defined a process that combines Flows to filter prime numbers. Then, we defined a Source that is an entry point of the stream processing and a Sink that triggers the actual processing.


If you find this article interesting, please check out our other articles.

Written by 

Asbin Bhadra is a Software consultant at Knoldus Inc. Knoldus does niche Reactive and Big Data product development on Scala, Spark, and Functional Java. He is recognized as a good team player, a dedicated and responsible professional, and a technology enthusiast. His current passions include utilizing the power of Scala, Akka, and Play to make Reactive systems. He has also contributed to various Akka, Play, and Scala-based templates.