Scala Akka Intro
Akka is a toolkit for building concurrent, distributed, and fault-tolerant systems on the JVM. It is built on the Actor Model — a programming model where independent units called actors communicate by sending messages to each other. Actors never share memory directly, which eliminates many concurrency bugs at the design level.
The Actor Model
Traditional concurrency: Actor Model:
──────────────────────────────── ────────────────────────────────
Threads share memory Actors have private state
Locks protect shared data No shared data — no locks
Race conditions possible Actors communicate via messages
Deadlocks possible Messages processed one at a time
Hard to scale across machines Actors can run on any machine
Actor diagram:
Actor A Actor B
┌─────────┐ ┌─────────┐
│ State │ message → │ State │
│ Behavior│ │ Behavior│
└─────────┘ └─────────┘
│ │
└── mailbox ───────────┘
(message queue)
Adding Akka to Your Project
// build.sbt
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor-typed" % "2.8.0",
"ch.qos.logback" % "logback-classic" % "1.4.7"
)
Your First Actor (Typed Akka)
import akka.actor.typed._
import akka.actor.typed.scaladsl._
// Step 1: Define the messages an actor can receive
sealed trait GreeterMessage
case class Greet(name: String) extends GreeterMessage
case class GreetMany(names: List[String]) extends GreeterMessage
// Step 2: Define the actor's behavior
object Greeter:
def apply(): Behavior[GreeterMessage] =
Behaviors.receiveMessage {
case Greet(name) =>
println(s"Hello, $name!")
Behaviors.same // stay in the same behavior
case GreetMany(names) =>
names.foreach(n => println(s"Hello, $n!"))
Behaviors.same
}
// Step 3: Create the actor system and send messages
@main def runGreeter(): Unit =
val system = ActorSystem(Greeter(), "greeter-system")
system ! Greet("Alice")
system ! Greet("Bob")
system ! GreetMany(List("Carol", "Dave", "Eve"))
Thread.sleep(500)
system.terminate()
Request-Reply Pattern
import akka.actor.typed._
import akka.actor.typed.scaladsl._
// Calculator actor
sealed trait CalcMsg
case class Add(a: Int, b: Int, replyTo: ActorRef[Int]) extends CalcMsg
case class Multiply(a: Int, b: Int, replyTo: ActorRef[Int]) extends CalcMsg
object Calculator:
def apply(): Behavior[CalcMsg] =
Behaviors.receiveMessage {
case Add(a, b, replyTo) =>
replyTo ! (a + b)
Behaviors.same
case Multiply(a, b, replyTo) =>
replyTo ! (a * b)
Behaviors.same
}
// Client actor that sends requests and receives replies
object Client:
def apply(calc: ActorRef[CalcMsg]): Behavior[Int] =
Behaviors.setup { ctx =>
calc ! Add(10, 5, ctx.self)
calc ! Multiply(4, 7, ctx.self)
Behaviors.receiveMessage { result =>
println(s"Got result: $result")
Behaviors.same
}
}
Client Actor Calculator Actor
──────────────── ────────────────────────────────
sends Add(10, 5, self) ──────► receives message
computes 10 + 5 = 15
receives 15 ◄────── sends 15 to replyTo (Client)
prints "Got result: 15"
Actor Hierarchy and Supervision
ActorSystem (guardian)
│
├── Worker Actor A
│ │
│ └── Child Actor A1
│
└── Worker Actor B
If a child actor crashes:
→ Parent receives failure signal
→ Parent decides: restart, stop, or escalate
→ System stays alive and resilient
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{Behavior, SupervisorStrategy}
object ResilientWorker:
def apply(): Behavior[String] =
Behaviors.supervise(
Behaviors.receiveMessage { msg =>
if msg == "crash" then throw new RuntimeException("Crashed!")
println(s"Processing: $msg")
Behaviors.same
}
).onFailure[RuntimeException](SupervisorStrategy.restart)
Key Akka Concepts
Concept Meaning
─────────────── ──────────────────────────────────────────
ActorSystem Top-level container; creates actors; lifecycle manager
Actor Independent unit with private state and a mailbox
Behavior What the actor does when it receives a message
ActorRef A reference (address) to send messages to an actor
! Send a message (fire and forget, non-blocking)
? Ask pattern — returns a Future with the reply
Mailbox Queue of unprocessed messages (FIFO)
Supervision Parent policy for handling child failures
When to Use Akka
Good use cases:
✓ Systems handling thousands of concurrent users
✓ Distributed systems across multiple machines
✓ Stateful background workers (session management, game state)
✓ Event-driven architectures
✓ Real-time data pipelines
Simpler alternatives for:
✗ Simple concurrent tasks → use Futures
✗ Batch processing → use parallel collections or Spark
✗ Single-threaded scripts → plain Scala
Akka Ecosystem
Akka Actors → core concurrency and state management
Akka Streams → reactive data pipeline processing
Akka HTTP → HTTP server and client
Akka Cluster → actors spread across multiple machines
Akka Persistence → actors that survive restarts (event sourcing)
Akka powers production systems at Twitter, LinkedIn, PayPal, and many other companies handling massive scale. Understanding the Actor Model fundamentals opens the door to building distributed systems that are resilient by design, not by accident.
