0Pricing
Scala for Backend Engineering & Functional Programming · Lesson

Supervision and Fault Tolerance

Explore Akka's supervision strategies to create resilient systems that can recover from failures gracefully.

Supervision and Fault Tolerance is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 3 of 3. 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 3 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Building Resilient Systems

In concurrent applications, things can go wrong. An actor might crash, a network call might fail, or a database might be unreachable.

Fault tolerance is the ability of a system to continue operating correctly even when parts of it fail. Akka provides powerful mechanisms to achieve this.

Parent-Child Supervision

Akka actors are organized in a hierarchy, much like a family tree. When an actor creates another actor, it becomes its parent.

  • Parents are responsible for supervising their children.
  • If a child actor fails, its parent receives a notification.
  • The parent then decides how to handle the child's failure. This is called a supervision strategy.

Deciding on Failure

When an actor fails, its parent can issue one of four directives:

  • Resume: The actor continues processing messages, ignoring the failure.
  • Restart: The actor is stopped, recreated, and then resumes processing. Its internal state is reset.
  • Stop: The actor is permanently terminated.
  • Escalate: The failure is passed up to the parent's supervisor (its grandparent).

OneForOne Strategy

The OneForOneStrategy is a common supervision strategy. It means that a directive applies only to the child actor that failed.

For example, if one child actor crashes, the parent might decide to restart only that specific child, leaving other children unaffected.

OneForOne in Action

Let's see OneForOneStrategy. We'll create a parent that restarts a child on an ArithmeticException but stops it on other exceptions.

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object OneForOneExample extends App {
  class Child extends Actor {
    override def preStart(): Unit = {
      println(s"Child ${self.path.name}: Started!")
    }
    override def postStop(): Unit = {
      println(s"Child ${self.path.name}: Stopped!")
    }
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
      println(s"Child ${self.path.name}: Pre-restart due to ${reason.getMessage}")
    }
    override def postRestart(reason: Throwable): Unit = {
      println(s"Child ${self.path.name}: Post-restart!")
    }
    def receive = {
      case "divideByZero" => throw new ArithmeticException("Oops, dividing by zero!")
      case "nullPointer"  => throw new NullPointerException("Something is null!")
      case msg            => println(s"Child ${self.path.name}: Received $msg")
    }
  }

  class Parent extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
        case _: ArithmeticException => Restart
        case _: NullPointerException => Stop
        case _                      => Escalate
      }

    val child = context.actorOf(Props[Child], "myChild")

    def receive = {
      case msg => child ! msg
    }
  }

  val system = ActorSystem("OneForOneSystem")
  val parent = system.actorOf(Props[Parent], "myParent")

  parent ! "Hello"
  parent ! "divideByZero" // Should cause a restart
  Thread.sleep(100)
  parent ! "Hello again"
  Thread.sleep(1000) // Give time for restart to complete

  parent ! "nullPointer"  // Should cause a stop
  Thread.sleep(100)
  parent ! "Hello after null" // This message will not be processed by child
  Thread.sleep(2000)

  system.terminate()
}

Observing OneForOne

Did you run the previous code? Here's what happened:

  • When "divideByZero" was sent, the child threw an ArithmeticException. The parent applied Restart. You saw "Pre-restart" and "Post-restart" logs.
  • The child then processed "Hello again".
  • When "nullPointer" was sent, the child threw a NullPointerException. The parent applied Stop. The child was terminated.
  • "Hello after null" was sent, but the child was already stopped and couldn't process it.

AllForOne Strategy

Sometimes, a failure in one child means that all sibling children might also be compromised or unable to function correctly.

The AllForOneStrategy applies the same directive to all children of the supervisor, not just the one that failed. If one child fails, all children are affected by the chosen directive.

AllForOne in Action

Let's modify our parent to use AllForOneStrategy. Notice how one child's failure affects its sibling.

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object AllForOneExample extends App {
  class Child extends Actor {
    override def preStart(): Unit = {
      println(s"Child ${self.path.name}: Started!")
    }
    override def postStop(): Unit = {
      println(s"Child ${self.path.name}: Stopped!")
    }
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
      println(s"Child ${self.path.name}: Pre-restart due to ${reason.getMessage}")
    }
    override def postRestart(reason: Throwable): Unit = {
      println(s"Child ${self.path.name}: Post-restart!")
    }
    def receive = {
      case "fail" => throw new RuntimeException("Child failed!")
      case msg    => println(s"Child ${self.path.name}: Received $msg")
    }
  }

  class Parent extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      AllForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
        case _: RuntimeException => Restart
        case _                   => Escalate
      }

    val childA = context.actorOf(Props[Child], "childA")
    val childB = context.actorOf(Props[Child], "childB")

    def receive = {
      case "failA" => childA ! "fail"
      case "msgB"  => childB ! "Hello from B"
      case msg     => println(s"Parent received: $msg")
    }
  }

  val system = ActorSystem("AllForOneSystem")
  val parent = system.actorOf(Props[Parent], "myParent")

  parent ! "msgB" // Child B receives a message
  Thread.sleep(100)

  parent ! "failA" // Child A fails, causing all children to restart
  Thread.sleep(1000)

  parent ! "msgB" // Child B (restarted) receives another message
  Thread.sleep(2000)

  system.terminate()
}

Customizing Deciders

You can create highly customized supervision logic using a decider function. This function takes a Throwable (the exception) and returns a Directive.

This allows you to implement complex rules based on the type of exception, the message that caused it, or even the state of the supervisor.

Advanced Supervision

Here's how to define a custom decider within your supervisor strategy for fine-grained control.

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object CustomDeciderExample extends App {
  class Worker extends Actor {
    def receive = {
      case "criticalError" => throw new IllegalStateException("Critical state!")
      case "minorError"    => throw new IllegalArgumentException("Bad input!")
      case msg             => println(s"Worker: Received $msg")
    }
  }

  class Supervisor extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 10.seconds) {
        case _: IllegalArgumentException => Resume // Minor error, just resume
        case _: IllegalStateException    => Restart // Critical, restart
        case _: Exception                => Stop    // Unknown, stop
        case _                           => Escalate // Others, escalate
      }

    val worker = context.actorOf(Props[Worker], "myWorker")

    def receive = {
      case msg => worker ! msg
    }
  }

  val system = ActorSystem("CustomDeciderSystem")
  val supervisor = system.actorOf(Props[Supervisor], "mySupervisor")

  supervisor ! "Hello"
  supervisor ! "minorError" // Should Resume
  Thread.sleep(100)
  supervisor ! "Hello again" // Worker should still be active
  Thread.sleep(500)

  supervisor ! "criticalError" // Should Restart
  Thread.sleep(100)
  supervisor ! "After critical" // Worker should be restarted
  Thread.sleep(1500)

  system.terminate()
}

Check Your Knowledge

Consider a parent actor supervising two child actors, childA and childB. The parent uses an AllForOneStrategy with the directive Restart for all exceptions.

If childA throws an exception, what will happen?

Recap: Fault Tolerance

Great job! In this lesson, you've learned about Akka's powerful supervision mechanisms:

  • Supervision Hierarchy: Parents supervise children.
  • Directives: Resume, Restart, Stop, Escalate.
  • OneForOneStrategy: Applies directives only to the failed child.
  • AllForOneStrategy: Applies directives to all children.
  • Custom Deciders: For fine-grained control over failure handling.

These tools are essential for building robust and resilient concurrent applications with Akka Actors!

Frequently asked questions

Is the “Supervision and Fault Tolerance” lesson free?

Yes — the full text of “Supervision and Fault Tolerance” is free to read here on the web, and the Scala for Backend Engineering & Functional Programming course includes 3 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 “Supervision and Fault Tolerance”?

Explore Akka's supervision strategies to create resilient systems that can recover from failures gracefully. 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 3 of 3, so you can start here or from the beginning and move at your own pace.

How long does the “Supervision and Fault Tolerance” 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. Akka Actor Model Fundamentals
  2. Designing Actor Systems
  3. Supervision and Fault Tolerance
← Back to Scala for Backend Engineering & Functional Programming