0Pricing
Scala for Backend Engineering & Functional Programming · Lesson

Running a Pipeline

Materialize and execute a graph.

Running a Pipeline is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Scala for Backend Engineering & Functional Programming learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

From Blueprint to Execution

So far the pipeline has been a pure blueprint. Materialization is the process that turns that blueprint into running actors that actually move data.

Nothing happens until you explicitly run the graph, which is what makes Akka Streams composable and reusable.

The ActorSystem

Materialization requires an ActorSystem, which provides the threads and dispatcher that back the stream's stages. In modern Akka, the system also acts as the implicit materializer.

One ActorSystem typically serves an entire application and many concurrent streams.

import akka.actor.ActorSystem

implicit val system: ActorSystem =
  ActorSystem("data-pipeline")
import system.dispatcher // ExecutionContext

runWith

The most direct way to run a Source is runWith, which attaches a Sink and materializes in one step, returning that Sink's materialized value.

Here the result is a Future[Int] that completes with the sum when the stream finishes.

import akka.stream.scaladsl.{Source, Sink}
import scala.concurrent.Future

val total: Future[Int] =
  Source(1 to 100).runWith(Sink.fold(0)(_ + _))

run on a RunnableGraph

If you have already built a closed RunnableGraph with to or toMat, call run() to materialize it. The return value is whatever materialized value the graph kept.

This separates pipeline construction from execution cleanly.

import akka.stream.scaladsl.Keep
import scala.concurrent.Future

val graph =
  Source(1 to 100)
    .toMat(Sink.fold(0)(_ + _))(Keep.right)

val result: Future[Int] = graph.run()

Convenience run Operators

Sources offer shortcuts: runForeach, runFold, and runReduce each attach the corresponding Sink and run immediately.

They are concise for common terminal operations on a Source.

import scala.concurrent.Future

val printed: Future[akka.Done] =
  Source(1 to 10).runForeach(println)

val sum: Future[Int] =
  Source(1 to 10).runFold(0)(_ + _)

Working with the Result Future

Terminal sinks return a Future that completes when the stream ends or fails. Register callbacks with onComplete to react to success or error.

Use the ActorSystem's dispatcher as the implicit ExecutionContext for these callbacks.

import scala.util.{Success, Failure}

total.onComplete {
  case Success(value) => println(s"Sum = $value")
  case Failure(ex)    => println(s"Failed: ${ex.getMessage}")
}

A Realistic Pipeline

A typical data pipeline reads from a Source, transforms with Flows, performs async I/O with mapAsync, batches with grouped, and writes to a Sink.

Each stage is small and the whole thing materializes with a single run.

val done =
  lineSource
    .map(parse)
    .mapAsync(4)(validate)
    .grouped(500)
    .runWith(bulkWriteSink)

Restarting Failed Streams

For resilience, wrap a Source or Flow with RestartSource.withBackoff so transient failures (a dropped connection) trigger an automatic restart with exponential backoff.

This keeps long-running ingestion pipelines alive without manual supervision.

import akka.stream.scaladsl.RestartSource
import akka.stream.RestartSettings
import scala.concurrent.duration._

val resilient = RestartSource.withBackoff(
  RestartSettings(1.second, 30.seconds, 0.2))(() => flakySource)

Graceful Shutdown with KillSwitch

A KillSwitch lets external code stop a running stream cleanly. Insert KillSwitches.single via viaMat and keep its materialized value to call shutdown() later.

This is essential for long-lived streams that must stop on application shutdown.

import akka.stream.{KillSwitches, KillSwitch}
import akka.stream.scaladsl.Keep

val (switch, done) =
  source
    .viaMat(KillSwitches.single)(Keep.right)
    .toMat(Sink.ignore)(Keep.both)
    .run()
// later: switch.shutdown()

Releasing Resources

When the application exits, terminate the ActorSystem to free its threads. Chain the terminate call after the stream's completion Future so shutdown is orderly.

Leaking an ActorSystem keeps the JVM alive and holds resources open.

done.onComplete { _ =>
  system.terminate()
}

Reusing the Materializer

Materializing the same blueprint multiple times creates independent running streams that share the ActorSystem's resources. The blueprint itself stays immutable and side-effect free.

This makes it safe to define a pipeline once and run it on demand for each incoming job.

val blueprint =
  Source(1 to 5).toMat(Sink.seq)(Keep.right)

val run1 = blueprint.run()
val run2 = blueprint.run() // independent execution

Quick Check

Consider what is required for a stream to actually process elements.

Recap

Running a pipeline means materializing a blueprint with an ActorSystem via run, runWith, or convenience operators, each returning a Future result.

You saw realistic multi-stage pipelines, automatic restarts with backoff, graceful shutdown via KillSwitch, resource cleanup with system.terminate(), and safe reuse of an immutable blueprint across independent runs.

Frequently asked questions

Is the “Running a Pipeline” lesson free?

Yes — the full text of “Running a Pipeline” is free to read here on the web, and the Scala for Backend Engineering & Functional Programming course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Scala for Backend Engineering & Functional Programming course, upgrade to CoddyKit PRO.

What will I learn in “Running a Pipeline”?

Materialize and execute a graph. You practise Scala for Backend Engineering & Functional Programming with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Scala for Backend Engineering & Functional Programming?

No prior experience is required. Scala for Backend Engineering & Functional Programming on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Running a Pipeline” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Scala for Backend Engineering & Functional Programming lesson?

Yes. Every Scala for Backend Engineering & Functional Programming lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Source, Flow, and Sink
  2. Transforming Streams
  3. Backpressure
  4. Running a Pipeline
← Back to Scala for Backend Engineering & Functional Programming