Akka Supervision Strategies: When to Resume, Restart, or Escalate a Failed Child
A child actor crashes mid-message. Should you Resume, Restart, Stop, or Escalate? Learn how to pick an Akka SupervisorStrategy based on state consistency, not just uptime.
05 Apr 2026, 07:56 UTC

A child actor throws an exception halfway through updating its internal state. Your supervisor now has to answer two questions at once: should the system keep running, and is the actor's memory still trustworthy? Picking the wrong answer either leaves corrupted data in place or traps the actor in a restart loop. The practical rule: treat an Akka SupervisorStrategy as a state-consistency decision first and an availability decision second.
The four decisions a supervisor can make
When a child fails, the supervisor does not catch the exception in the child. Instead, the failure is converted into a signal that the parent handles. There are four built-in decisions:
- Resume keeps the same actor instance and its internal state, and discards the failed message. This is appropriate when the exception is transient—say, a temporary timeout—and you have reason to believe the actor's fields were not mutated before the throw.
- Restart destroys the actor instance and creates a fresh one from the original
Props. All internal state is lost. Use this when the exception may have left the actor in an inconsistent state, or when the actor is cheap to rebuild. - Stop terminates the child permanently. Use this when the failure is unrecoverable or the actor has no reason to exist anymore.
- Escalate passes the failure to the parent's own supervisor. Use this when the current supervisor lacks the context to make a safe decision.
Scope: One-for-One versus All-for-One
The decision above is only half the configuration. You also choose the blast radius. OneForOneStrategy applies the decision only to the child that failed. AllForOneStrategy applies the same decision to every child under that supervisor. One-for-One is the safer default because it isolates the failure. All-for-One makes sense when children share correlated state or a common resource, but it means one bad actor can take down healthy siblings.
Worked example: Restart versus Resume with a counter
The clearest way to see the difference is to give a child a mutable counter, force an exception, and then ask the child for its value. The following classic Akka actor pattern shows the shape of the configuration. Place the strategy override in the parent actor class; no special permissions are required beyond normal actor-system deployment.
import akka.actor.{Actor, ActorLogging, OneForOneStrategy, Props, SupervisorStrategy}
import akka.actor.SupervisorStrategy.Restart
import scala.concurrent.duration._
class Child extends Actor with ActorLogging {
var counter = 0
def receive: Receive = {
case "increment" =>
counter += 1
if (counter > 3) throw new IllegalStateException("Threshold reached")
log.info("Counter is {}", counter)
case "get" => sender() ! counter
}
}
class Parent extends Actor with ActorLogging {
override val supervisorStrategy: SupervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
case _: IllegalStateException => Restart
}
val child = context.actorOf(Props[Child](), "child")
def receive: Receive = {
case msg => child forward msg
}
}
Expected check: send "increment" four times, then send "get". With Restart, the fourth increment triggers a new Child instance, so the subsequent "get" should reflect a fresh counter. If you change the decision to Resume, the same instance survives and the counter retains the value it held before the exception. The maxNrOfRetries and withinTimeRange parameters bound the restart loop: if the child fails more than ten times within a minute, the actor system stops it rather than restarting indefinitely.
Relevant risk: if the exception is caused by a persistent condition—bad configuration, a full disk, a malformed message that arrives repeatedly—Restart will burn through the retry budget and then stop the actor. That may be the correct outcome, but it should be a deliberate choice, not a surprise.
Trade-offs and limitations
Supervision is powerful, but it is not free.
Restart loops hide root causes. A strategy that restarts on every exception can mask a systemic problem. The actor appears healthy because it keeps coming back, yet the underlying fault remains. Always pair restart limits with monitoring of restart frequency.
Resume can preserve corruption. If the exception was thrown halfway through a multi-step state mutation, keeping the actor alive means keeping partial writes. Resume is only safe when you can prove the failed message did not alter state before the throw.
Deep hierarchies obscure failure flow. When supervisors escalate to their own parents, the original failure signal travels up the tree. In a deep hierarchy, it becomes difficult to trace which supervisor applied which decision and why. Keep hierarchies shallow where possible and log escalation decisions explicitly.
An actionable way to choose
Before you write a strategy, ask one question per exception type: after this throw, is the actor's internal state still valid? If the answer is no, choose Restart. If the answer is yes, choose Resume. If you cannot answer because the failure belongs to a wider context, choose Escalate. Then verify your choice by injecting the exception in a test environment, querying the actor's state after the failure, and checking the lifecycle logs to confirm the strategy behaved as intended.
The classic actor APIs shown here reflect long-standing Akka behavior; confirm exact import paths and default retry values against your project's Akka version before relying on them in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.