Style guide

This is a style guide with recommendations of idioms and patterns for writing Akka actors. Note that this guide does not cover the classic actor API.

As with all style guides, treat this as a list of rules to be broken. There are certainly times when alternative styles should be preferred over the ones given here.

Functional versus object-oriented style

There are two flavors of the Actor APIs.

  1. The functional programming style where you pass a function to a factory which then constructs a behavior, for stateful actors this means passing immutable state around as parameters and switching to a new behavior whenever you need to act on a changed state.
  2. The object-oriented style where a concrete class for the actor behavior is defined and mutable state is kept inside of it as fields.

An example of a counter actor implemented in the functional style:

Scala
source
import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors object Counter { sealed trait Command case object Increment extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class Value(n: Int) def apply(): Behavior[Command] = counter(0) private def counter(n: Int): Behavior[Command] = Behaviors.receive { (context, message) => message match { case Increment => val newValue = n + 1 context.log.debug("Incremented counter to [{}]", newValue) counter(newValue) case GetValue(replyTo) => replyTo ! Value(n) Behaviors.same } } }
Java
sourceimport akka.actor.typed.Behavior;
import akka.actor.typed.SupervisorStrategy;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;

public class Counter {
  public interface Command {}

  public enum Increment implements Command {
    INSTANCE
  }

  public static class GetValue implements Command {
    public final ActorRef<Value> replyTo;

    public GetValue(ActorRef<Value> replyTo) {
      this.replyTo = replyTo;
    }
  }

  public static class Value {
    public final int value;

    public Value(int value) {
      this.value = value;
    }
  }

  public static Behavior<Command> create() {
    return Behaviors.setup(context -> counter(context, 0));
  }

  private static Behavior<Command> counter(final ActorContext<Command> context, final int n) {

    return Behaviors.receive(Command.class)
        .onMessage(Increment.class, notUsed -> onIncrement(context, n))
        .onMessage(GetValue.class, command -> onGetValue(n, command))
        .build();
  }

  private static Behavior<Command> onIncrement(ActorContext<Command> context, int n) {
    int newValue = n + 1;
    context.getLog().debug("Incremented counter to [{}]", newValue);
    return counter(context, newValue);
  }

  private static Behavior<Command> onGetValue(int n, GetValue command) {
    command.replyTo.tell(new Value(n));
    return Behaviors.same();
  }
}

Corresponding actor implemented in the object-oriented style:

Scala
source
import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.AbstractBehavior import org.slf4j.Logger object Counter { sealed trait Command case object Increment extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class Value(n: Int) def apply(): Behavior[Command] = { Behaviors.setup(context => new Counter(context)) } } class Counter(context: ActorContext[Counter.Command]) extends AbstractBehavior[Counter.Command](context) { import Counter._ private var n = 0 override def onMessage(msg: Command): Behavior[Counter.Command] = { msg match { case Increment => n += 1 context.log.debug("Incremented counter to [{}]", n) this case GetValue(replyTo) => replyTo ! Value(n) this } } }
Java
sourceimport akka.actor.typed.Behavior;
import akka.actor.typed.SupervisorStrategy;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.AbstractBehavior;
import akka.actor.typed.javadsl.Receive;

public class Counter extends AbstractBehavior<Counter.Command> {

  public interface Command {}

  public enum Increment implements Command {
    INSTANCE
  }

  public static class GetValue implements Command {
    public final ActorRef<Value> replyTo;

    public GetValue(ActorRef<Value> replyTo) {
      this.replyTo = replyTo;
    }
  }

  public static class Value {
    public final int value;

    public Value(int value) {
      this.value = value;
    }
  }

  public static Behavior<Command> create() {
    return Behaviors.setup(Counter::new);
  }

  private int n;

  private Counter(ActorContext<Command> context) {
    super(context);
  }

  @Override
  public Receive<Command> createReceive() {
    return newReceiveBuilder()
        .onMessage(Increment.class, notUsed -> onIncrement())
        .onMessage(GetValue.class, this::onGetValue)
        .build();
  }

  private Behavior<Command> onIncrement() {
    n++;
    getContext().getLog().debug("Incremented counter to [{}]", n);
    return this;
  }

  private Behavior<Command> onGetValue(GetValue command) {
    command.replyTo.tell(new Value(n));
    return this;
  }
}

Some similarities to note:

  • Messages are defined in the same way.
  • Both have an apply factory method in the companion objecta static create factory method to create the initial behavior, i.e. from the outside they are used in the same way.
  • Pattern matchingMatching and handling of the messages are done in the same way.
  • The ActorContext API is the same.

A few differences to note:

  • There is no class in the functional style, but that is not strictly a requirement and sometimes it’s convenient to use a class also with the functional style to reduce number of parameters in the methods.
  • Mutable state, such as the var nint n is typically used in the object-oriented style.
  • In the functional style the state is updated by returning a new behavior that holds the new immutable state, the n: Intfinal int n parameter of the counter method.
  • The object-oriented style must use a new instance of the initial Behavior for each spawned actor instance, since the state in AbstractBehavior instance must not be shared between actor instances. This is “hidden” in the functional style since the immutable state is captured by the function.
  • In the object-oriented style one can return this to stay with the same behavior for next message. In the functional style there is no this so Behaviors.same is used instead.
  • The ActorContext is accessed in different ways. In the object-oriented style it’s retrieved from Behaviors.setup and kept as an instance field, while in the functional style it’s passed in alongside the message. That said, Behaviors.setup is often used in the functional style as well, and then often together with Behaviors.receiveMessage that doesn’t pass in the context with the message. The ActorContext is accessed with Behaviors.setup but then kept in different ways. As an instance field versus a method parameter.

Which style you choose to use is a matter of taste and both styles can be mixed depending on which is best for a specific actor. An actor can switch between behaviors implemented in different styles. For example, it may have an initial behavior that is only stashing messages until some initial query has been completed and then switching over to its main active behavior that is maintaining some mutable state. Such initial behavior is nice in the functional style and the active behavior may be better with the object-oriented style.

We would recommend using the tool that is best for the job. The APIs are similar in many ways to make it easy to learn both. You may of course also decide to just stick to one style for consistency and familiarity reasons.

When developing in Scala the functional style will probably be the choice for many.

Some reasons why you may want to use the functional style:

  • You are familiar with a functional approach of structuring the code. Note that this API is still not using any advanced functional programming or type theory constructs.
  • The state is immutable and can be passed to “next” behavior.
  • The Behavior is stateless.
  • The actor lifecycle has several different phases that can be represented by switching between different behaviors, like a finite state machine. This is also supported with the object-oriented style, but it’s typically nicer with the functional style.
  • It’s less risk of accessing mutable state in the actor from other threads, like Future or Streams callbacks.

Some reasons why you may want to use the object-oriented style:

  • You are more familiar with an object-oriented style of structuring the code with methods in a class rather than functions.
  • Some state is not immutable.
  • It could be more familiar and easier to upgrade existing classic actors to this style.
  • Mutable state can sometimes have better performance, e.g. mutable collections and avoiding allocating new instance for next behavior (be sure to benchmark if this is your motivation).

When developing in Java the object-oriented style will probably be the choice for many.

Some reasons why you may want to use the object-oriented style:

  • You are more familiar with an object-oriented style of structuring the code with methods in a class rather than functions.
  • Java lambdas can only close over final or effectively final fields, making it impractical to use the functional style in behaviors that mutate their fields.
  • Some state is not immutable, e.g. immutable collections are not widely used in Java. It is OK to use mutable state also with the functional style but you must make sure that it’s not shared between different actor instances.
  • It could be more familiar and easier to upgrade existing classic actors to this style.
  • Mutable state can sometimes have better performance, e.g. mutable collections and avoiding allocating new instance for next behavior (be sure to benchmark if this is your motivation).

Some reasons why you may want to use the functional style:

  • You are familiar with a functional approach of structuring the code. Note that this API is still not using any advanced functional programming or type theory constructs.
  • The state is immutable and can be passed to “next” behavior.
  • The Behavior is stateless.
  • The actor lifecycle has several different phases that can be represented by switching between different behaviors, like a finite state machine. This is also supported with the object-oriented style, but it’s typically nicer with the functional style.
  • It’s less risk of accessing mutable state in the actor from other threads, like CompletionStage or Streams callbacks.

Passing around too many parameters

One thing you will quickly run into when using the functional style is that you need to pass around many parameters.

Let’s add name parameter and timers to the previous Counter example. A first approach would be to just add those as separate parameters:

Scala
source
// this is an anti-example, better solutions exists object Counter { sealed trait Command case object Increment extends Command final case class IncrementRepeatedly(interval: FiniteDuration) extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class Value(n: Int) def apply(name: String): Behavior[Command] = Behaviors.withTimers { timers => counter(name, timers, 0) } private def counter(name: String, timers: TimerScheduler[Command], n: Int): Behavior[Command] = Behaviors.receive { (context, message) => message match { case IncrementRepeatedly(interval) => context.log.debug( "[{}] Starting repeated increments with interval [{}], current count is [{}]", name, interval.toString, n.toString) timers.startTimerWithFixedDelay(Increment, interval) Behaviors.same case Increment => val newValue = n + 1 context.log.debug("[{}] Incremented counter to [{}]", name, newValue) counter(name, timers, newValue) case GetValue(replyTo) => replyTo ! Value(n) Behaviors.same } } }
Java
source
// this is an anti-example, better solutions exists public class Counter { public interface Command {} public static class IncrementRepeatedly implements Command { public final Duration interval; public IncrementRepeatedly(Duration interval) { this.interval = interval; } } public enum Increment implements Command { INSTANCE } public static class GetValue implements Command { public final ActorRef<Value> replyTo; public GetValue(ActorRef<Value> replyTo) { this.replyTo = replyTo; } } public static class Value { public final int value; public Value(int value) { this.value = value; } } public static Behavior<Command> create(String name) { return Behaviors.setup( context -> Behaviors.withTimers(timers -> counter(name, context, timers, 0))); } private static Behavior<Command> counter( final String name, final ActorContext<Command> context, final TimerScheduler<Command> timers, final int n) { return Behaviors.receive(Command.class) .onMessage( IncrementRepeatedly.class, command -> onIncrementRepeatedly(name, context, timers, n, command)) .onMessage(Increment.class, notUsed -> onIncrement(name, context, timers, n)) .onMessage(GetValue.class, command -> onGetValue(n, command)) .build(); } private static Behavior<Command> onIncrementRepeatedly( String name, ActorContext<Command> context, TimerScheduler<Command> timers, int n, IncrementRepeatedly command) { context .getLog() .debug( "[{}] Starting repeated increments with interval [{}], current count is [{}]", name, command.interval, n); timers.startTimerWithFixedDelay(Increment.INSTANCE, command.interval); return Behaviors.same(); } private static Behavior<Command> onIncrement( String name, ActorContext<Command> context, TimerScheduler<Command> timers, int n) { int newValue = n + 1; context.getLog().debug("[{}] Incremented counter to [{}]", name, newValue); return counter(name, context, timers, newValue); } private static Behavior<Command> onGetValue(int n, GetValue command) { command.replyTo.tell(new Value(n)); return Behaviors.same(); } }

Ouch, that doesn’t look good. More things may be needed, such as stashing or application specific “constructor” parameters. As you can imagine, that will be too much boilerplate.

As a first step we can place all these parameters in a class so that we at least only have to pass around one thing. Still good to have the “changing” state, the n: Intfinal int n here, as a separate parameter.

Scala
source
// this is better than previous example, but even better solution exists object Counter { sealed trait Command case object Increment extends Command final case class IncrementRepeatedly(interval: FiniteDuration) extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class Value(n: Int) private case class Setup(name: String, context: ActorContext[Command], timers: TimerScheduler[Command]) def apply(name: String): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => counter(Setup(name, context, timers), 0) } } private def counter(setup: Setup, n: Int): Behavior[Command] = Behaviors.receiveMessage { case IncrementRepeatedly(interval) => setup.context.log.debug( "[{}] Starting repeated increments with interval [{}], current count is [{}]", setup.name, interval, n) setup.timers.startTimerWithFixedDelay(Increment, interval) Behaviors.same case Increment => val newValue = n + 1 setup.context.log.debug("[{}] Incremented counter to [{}]", setup.name, newValue) counter(setup, newValue) case GetValue(replyTo) => replyTo ! Value(n) Behaviors.same } }
Java
source
// this is better than previous example, but even better solution exists public class Counter { // messages omitted for brevity, same messages as above example private static class Setup { final String name; final ActorContext<Command> context; final TimerScheduler<Command> timers; private Setup(String name, ActorContext<Command> context, TimerScheduler<Command> timers) { this.name = name; this.context = context; this.timers = timers; } } public static Behavior<Command> create(String name) { return Behaviors.setup( context -> Behaviors.withTimers(timers -> counter(new Setup(name, context, timers), 0))); } private static Behavior<Command> counter(final Setup setup, final int n) { return Behaviors.receive(Command.class) .onMessage( IncrementRepeatedly.class, command -> onIncrementRepeatedly(setup, n, command)) .onMessage(Increment.class, notUsed -> onIncrement(setup, n)) .onMessage(GetValue.class, command -> onGetValue(n, command)) .build(); } private static Behavior<Command> onIncrementRepeatedly( Setup setup, int n, IncrementRepeatedly command) { setup .context .getLog() .debug( "[{}] Starting repeated increments with interval [{}], current count is [{}]", setup.name, command.interval, n); setup.timers.startTimerWithFixedDelay(Increment.INSTANCE, command.interval); return Behaviors.same(); } private static Behavior<Command> onIncrement(Setup setup, int n) { int newValue = n + 1; setup.context.getLog().debug("[{}] Incremented counter to [{}]", setup.name, newValue); return counter(setup, newValue); } private static Behavior<Command> onGetValue(int n, GetValue command) { command.replyTo.tell(new Value(n)); return Behaviors.same(); } }

That’s better. Only one thing to carry around and easy to add more things to it without rewriting everything. Note that we also placed the ActorContext in the Setup class, and therefore switched from Behaviors.receive to Behaviors.receiveMessage since we already have access to the context.

It’s still rather annoying to have to pass the same thing around everywhere.

We can do better by introducing an enclosing class, even though it’s still using the functional style. The “constructor” parameters can be immutablefinal instance fields and can be accessed from member methods.

Scala
source
// this is better than previous examples object Counter { sealed trait Command case object Increment extends Command final case class IncrementRepeatedly(interval: FiniteDuration) extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class Value(n: Int) def apply(name: String): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => new Counter(name, context, timers).counter(0) } } } class Counter private ( name: String, context: ActorContext[Counter.Command], timers: TimerScheduler[Counter.Command]) { import Counter._ private def counter(n: Int): Behavior[Command] = Behaviors.receiveMessage { case IncrementRepeatedly(interval) => context.log.debug( "[{}] Starting repeated increments with interval [{}], current count is [{}]", name, interval, n) timers.startTimerWithFixedDelay(Increment, interval) Behaviors.same case Increment => val newValue = n + 1 context.log.debug("[{}] Incremented counter to [{}]", name, newValue) counter(newValue) case GetValue(replyTo) => replyTo ! Value(n) Behaviors.same } }
Java
source
// this is better than previous examples public class Counter { // messages omitted for brevity, same messages as above example public static Behavior<Command> create(String name) { return Behaviors.setup( context -> Behaviors.withTimers(timers -> new Counter(name, context, timers).counter(0))); } private final String name; private final ActorContext<Command> context; private final TimerScheduler<Command> timers; private Counter(String name, ActorContext<Command> context, TimerScheduler<Command> timers) { this.name = name; this.context = context; this.timers = timers; } private Behavior<Command> counter(final int n) { return Behaviors.receive(Command.class) .onMessage(IncrementRepeatedly.class, command -> onIncrementRepeatedly(n, command)) .onMessage(Increment.class, notUsed -> onIncrement(n)) .onMessage(GetValue.class, command -> onGetValue(n, command)) .build(); } private Behavior<Command> onIncrementRepeatedly(int n, IncrementRepeatedly command) { context .getLog() .debug( "[{}] Starting repeated increments with interval [{}], current count is [{}]", name, command.interval, n); timers.startTimerWithFixedDelay(Increment.INSTANCE, command.interval); return Behaviors.same(); } private Behavior<Command> onIncrement(int n) { int newValue = n + 1; context.getLog().debug("[{}] Incremented counter to [{}]", name, newValue); return counter(newValue); } private Behavior<Command> onGetValue(int n, GetValue command) { command.replyTo.tell(new Value(n)); return Behaviors.same(); } }

That’s nice. One thing to be cautious with here is that it’s important that you create a new instance for each spawned actor, since those parameters must not be shared between different actor instances. That comes natural when creating the instance from Behaviors.setup as in the above example. Having a apply factory method in the companion object and making the constructor private is recommended. static create factory method and making the constructor private is recommended.

This can also be useful when testing the behavior by creating a test subclass that overrides certain methods in the class. The test would create the instance without the apply factory methodstatic create factory method. Then you need to relax the visibility constraints of the constructor and methods.

It’s not recommended to place mutable state and var membersnon-final members in the enclosing class. It would be correct from an actor thread-safety perspective as long as the same instance of the enclosing class is not shared between different actor instances, but if that is what you need you should rather use the object-oriented style with the AbstractBehavior class.

Similar can be achieved without an enclosing class by placing the def counter inside the Behaviors.setup block. That works fine, but for more complex behaviors it can be better to structure the methods in a class. For completeness, here is how it would look like:

Scala
source
// this works, but previous example is better for structuring more complex behaviors object Counter { sealed trait Command case object Increment extends Command final case class IncrementRepeatedly(interval: FiniteDuration) extends Command final case class GetValue(replyTo: ActorRef[Value]) extends Command final case class Value(n: Int) def apply(name: String): Behavior[Command] = Behaviors.setup { context => Behaviors.withTimers { timers => def counter(n: Int): Behavior[Command] = Behaviors.receiveMessage { case IncrementRepeatedly(interval) => context.log.debug( "[{}] Starting repeated increments with interval [{}], current count is [{}]", name, interval, n) timers.startTimerWithFixedDelay(Increment, interval) Behaviors.same case Increment => val newValue = n + 1 context.log.debug("[{}] Incremented counter to [{}]", name, newValue) counter(newValue) case GetValue(replyTo) => replyTo ! Value(n) Behaviors.same } counter(0) } } }

Behavior factory method

The initial behavior should be created via a factory method in the companion objecta static factory method. Thereby the usage of the behavior doesn’t change when the implementation is changed, for example if changing between object-oriented and function style.

The factory method is a good place for retrieving resources like Behaviors.withTimers, Behaviors.withStash and ActorContext with Behaviors.setup.

When using the object-oriented style, AbstractBehavior, a new instance should be created from a Behaviors.setup block in this factory method even though the ActorContext is not needed. This is important because a new instance should be created when restart supervision is used. Typically, the ActorContext is needed anyway.

The naming convention for the factory method is apply (when using Scala)create (when using Java). Consistent naming makes it easier for readers of the code to find the “starting point” of the behavior.

In the functional style the factory could even have been defined as a valstatic field if all state is immutable and captured by the function, but since most behaviors need some initialization parameters it is preferred to consistently use a method (def) for the factory.

Example:

Scala
sourceobject CountDown {
  sealed trait Command
  case object Down extends Command

  // factory for the initial `Behavior`
  def apply(countDownFrom: Int, notifyWhenZero: ActorRef[Done]): Behavior[Command] =
    new CountDown(notifyWhenZero).counter(countDownFrom)
}

private class CountDown(notifyWhenZero: ActorRef[Done]) {
  import CountDown._

  private def counter(remaining: Int): Behavior[Command] = {
    Behaviors.receiveMessage {
      case Down =>
        if (remaining == 1) {
          notifyWhenZero.tell(Done)
          Behaviors.stopped
        } else
          counter(remaining - 1)
    }
  }

}
Java
sourcepublic class CountDown extends AbstractBehavior<CountDown.Command> {

  public interface Command {}

  public enum Down implements Command {
    INSTANCE
  }

  // factory for the initial `Behavior`
  public static Behavior<Command> create(int countDownFrom, ActorRef<Done> notifyWhenZero) {
    return Behaviors.setup(context -> new CountDown(context, countDownFrom, notifyWhenZero));
  }

  private final ActorRef<Done> notifyWhenZero;
  private int remaining;

  private CountDown(
      ActorContext<Command> context, int countDownFrom, ActorRef<Done> notifyWhenZero) {
    super(context);
    this.remaining = countDownFrom;
    this.notifyWhenZero = notifyWhenZero;
  }

  @Override
  public Receive<Command> createReceive() {
    return newReceiveBuilder().onMessage(Down.class, notUsed -> onDown()).build();
  }

  private Behavior<Command> onDown() {
    remaining--;
    if (remaining == 0) {
      notifyWhenZero.tell(Done.getInstance());
      return Behaviors.stopped();
    } else {
      return this;
    }
  }
}

When spawning an actor from this initial behavior it looks like:

Scala
sourceval countDown = context.spawn(CountDown(100, doneRef), "countDown")
Java
sourceActorRef<CountDown.Command> countDown =
    context.spawn(CountDown.create(100, doneRef), "countDown");

Where to define messages

When sending or receiving actor messages they should be prefixed with the name of the actor/behavior that defines them to avoid ambiguities.

Scala
sourcecountDown ! CountDown.Down
Java
sourcecountDown.tell(CountDown.Down.INSTANCE);

Such a style is preferred over using importing Down and using countDown ! Down importing Down and using countDown.tell(Down.INSTANCE);. However, within the Behavior that handle these messages the short names can be used.

Therefore it is not recommended to define messages as top-level classes.

For the majority of cases it’s good style to define the messages in the companion objectas static inner classes together with the Behavior.

Scala
sourceobject Counter {
  sealed trait Command
  case object Increment extends Command
  final case class GetValue(replyTo: ActorRef[Value]) extends Command
  final case class Value(n: Int)
}
Java
sourcepublic class Counter extends AbstractBehavior<Counter.Command> {

  public interface Command {}

  public enum Increment implements Command {
    INSTANCE
  }

  public static class GetValue implements Command {
    public final ActorRef<Value> replyTo;

    public GetValue(ActorRef<Value> replyTo) {
      this.replyTo = replyTo;
    }
  }

  public static class Value {
    public final int value;

    public Value(int value) {
      this.value = value;
    }
  }
}

If several actors share the same message protocol, it’s recommended to define those messages in a separate objectinterface for that protocol.

Here’s an example of a shared message protocol setup:

Scala
sourceobject CounterProtocol {
  sealed trait Command

  final case class Increment(delta: Int, replyTo: ActorRef[OperationResult]) extends Command
  final case class Decrement(delta: Int, replyTo: ActorRef[OperationResult]) extends Command

  sealed trait OperationResult
  case object Confirmed extends OperationResult
  final case class Rejected(reason: String) extends OperationResult
}
Java
sourceinterface CounterProtocol {
  interface Command {}

  public static class Increment implements Command {
    public final int delta;
    private final ActorRef<OperationResult> replyTo;

    public Increment(int delta, ActorRef<OperationResult> replyTo) {
      this.delta = delta;
      this.replyTo = replyTo;
    }
  }

  public static class Decrement implements Command {
    public final int delta;
    private final ActorRef<OperationResult> replyTo;

    public Decrement(int delta, ActorRef<OperationResult> replyTo) {
      this.delta = delta;
      this.replyTo = replyTo;
    }
  }

  interface OperationResult {}

  enum Confirmed implements OperationResult {
    INSTANCE
  }

  public static class Rejected implements OperationResult {
    public final String reason;

    public Rejected(String reason) {
      this.reason = reason;
    }
  }
}

Note that the response message hierarchy in this case could be completely avoided by using the API instead (see Generic Response Wrapper).

Public versus private messages

Often an actor has some messages that are only for its internal implementation and not part of the public message protocol, such as timer messages or wrapper messages for ask or messageAdapter.

Such messages should be declared private so they can’t be accessed and sent from the outside of the actor. Note that they must still extendimplement the public Command traitinterface.

Here is an example of using private for an internal message:

Scala
sourceobject Counter {
  sealed trait Command
  case object Increment extends Command
  final case class GetValue(replyTo: ActorRef[Value]) extends Command
  final case class Value(n: Int)

  // Tick is private so can't be sent from the outside
  private case object Tick extends Command

  def apply(name: String, tickInterval: FiniteDuration): Behavior[Command] =
    Behaviors.setup { context =>
      Behaviors.withTimers { timers =>
        timers.startTimerWithFixedDelay(Tick, tickInterval)
        new Counter(name, context).counter(0)
      }
    }
}

class Counter private (name: String, context: ActorContext[Counter.Command]) {
  import Counter._

  private def counter(n: Int): Behavior[Command] =
    Behaviors.receiveMessage {
      case Increment =>
        val newValue = n + 1
        context.log.debug("[{}] Incremented counter to [{}]", name, newValue)
        counter(newValue)
      case Tick =>
        val newValue = n + 1
        context.log.debug("[{}] Incremented counter by background tick to [{}]", name, newValue)
        counter(newValue)
      case GetValue(replyTo) =>
        replyTo ! Value(n)
        Behaviors.same
    }
}
Java
sourcepublic class Counter extends AbstractBehavior<Counter.Command> {

  public interface Command {}

  public enum Increment implements Command {
    INSTANCE
  }

  public static class GetValue implements Command {
    public final ActorRef<Value> replyTo;

    public GetValue(ActorRef<Value> replyTo) {
      this.replyTo = replyTo;
    }
  }

  public static class Value {
    public final int value;

    public Value(int value) {
      this.value = value;
    }
  }

  // Tick is private so can't be sent from the outside
  private enum Tick implements Command {
    INSTANCE
  }

  public static Behavior<Command> create(String name, Duration tickInterval) {
    return Behaviors.setup(
        context ->
            Behaviors.withTimers(
                timers -> {
                  timers.startTimerWithFixedDelay(Tick.INSTANCE, tickInterval);
                  return new Counter(name, context);
                }));
  }

  private final String name;
  private int count;

  private Counter(String name, ActorContext<Command> context) {
    super(context);
    this.name = name;
  }

  @Override
  public Receive<Command> createReceive() {
    return newReceiveBuilder()
        .onMessage(Increment.class, notUsed -> onIncrement())
        .onMessage(Tick.class, notUsed -> onTick())
        .onMessage(GetValue.class, this::onGetValue)
        .build();
  }


  private Behavior<Command> onIncrement() {
    count++;
    getContext().getLog().debug("[{}] Incremented counter to [{}]", name, count);
    return this;
  }

  private Behavior<Command> onTick() {
    count++;
    getContext()
        .getLog()
        .debug("[{}] Incremented counter by background tick to [{}]", name, count);
    return this;
  }

  private Behavior<Command> onGetValue(GetValue command) {
    command.replyTo.tell(new Value(count));
    return this;
  }

}

An alternative approach is using a type hierarchy and narrow to have a super-type for the public messages as a distinct type from the super-type of all actor messages. The former approach is recommended but it is good to know this alternative as it can be useful when using shared message protocol classes as described in Where to define messages.

Here’s an example of using a type hierarchy to separate public and private messages:

Scala
source// above example is preferred, but this is possible and not wrong
object Counter {
  // The type of all public and private messages the Counter actor handles
  sealed trait Message

  /** Counter's public message protocol type. */
  sealed trait Command extends Message
  case object Increment extends Command
  final case class GetValue(replyTo: ActorRef[Value]) extends Command
  final case class Value(n: Int)

  // The type of the Counter actor's internal messages.
  sealed trait PrivateCommand extends Message
  // Tick is a private command so can't be sent to an ActorRef[Command]
  case object Tick extends PrivateCommand

  def apply(name: String, tickInterval: FiniteDuration): Behavior[Command] = {
    Behaviors
      .setup[Counter.Message] { context =>
        Behaviors.withTimers { timers =>
          timers.startTimerWithFixedDelay(Tick, tickInterval)
          new Counter(name, context).counter(0)
        }
      }
      .narrow // note narrow here
  }
}

class Counter private (name: String, context: ActorContext[Counter.Message]) {
  import Counter._

  private def counter(n: Int): Behavior[Message] =
    Behaviors.receiveMessage {
      case Increment =>
        val newValue = n + 1
        context.log.debug("[{}] Incremented counter to [{}]", name, newValue)
        counter(newValue)
      case Tick =>
        val newValue = n + 1
        context.log.debug("[{}] Incremented counter by background tick to [{}]", name, newValue)
        counter(newValue)
      case GetValue(replyTo) =>
        replyTo ! Value(n)
        Behaviors.same
    }
}
Java
source// above example is preferred, but this is possible and not wrong
public class Counter extends AbstractBehavior<Counter.Message> {

  // The type of all public and private messages the Counter actor handles
  public interface Message {}

  /** Counter's public message protocol type. */
  public interface Command extends Message {}

  public enum Increment implements Command {
    INSTANCE
  }

  public static class GetValue implements Command {
    public final ActorRef<Value> replyTo;

    public GetValue(ActorRef<Value> replyTo) {
      this.replyTo = replyTo;
    }
  }

  public static class Value {
    public final int value;

    public Value(int value) {
      this.value = value;
    }
  }

  // The type of the Counter actor's internal messages.
  interface PrivateCommand extends Message {}

  // Tick is a private command so can't be sent to an ActorRef<Command>
  enum Tick implements PrivateCommand {
    INSTANCE
  }

  public static Behavior<Command> create(String name, Duration tickInterval) {
    return Behaviors.setup(
            (ActorContext<Message> context) ->
                Behaviors.withTimers(
                    timers -> {
                      timers.startTimerWithFixedDelay(Tick.INSTANCE, tickInterval);
                      return new Counter(name, context);
                    }))
        .narrow(); // note narrow here
  }

  private final String name;
  private int count;

  private Counter(String name, ActorContext<Message> context) {
    super(context);
    this.name = name;
  }

  @Override
  public Receive<Message> createReceive() {
    return newReceiveBuilder()
        .onMessage(Increment.class, notUsed -> onIncrement())
        .onMessage(Tick.class, notUsed -> onTick())
        .onMessage(GetValue.class, this::onGetValue)
        .build();
  }

  private Behavior<Message> onIncrement() {
    count++;
    getContext().getLog().debug("[{}] Incremented counter to [{}]", name, count);
    return this;
  }

  private Behavior<Message> onTick() {
    count++;
    getContext()
        .getLog()
        .debug("[{}] Incremented counter by background tick to [{}]", name, count);
    return this;
  }

  private Behavior<Message> onGetValue(GetValue command) {
    command.replyTo.tell(new Value(count));
    return this;
  }
}

private visibility can be defined for the PrivateCommand messages but it’s not strictly needed since they can’t be sent to an ActorRef[Command]ActorRef, which is the public message type of the actor.

Singleton messages

For messages without parameters the enum singleton pattern is recommended:

Java
sourcepublic enum Increment implements Command {
  INSTANCE
}

In the ReceiveBuilder it can be matched in same way as other messages:

Java
source.onMessage(Increment.class, notUsed -> onIncrement())

Lambdas versus method references

It’s recommended to keep the message matching with the ReceiveBuilder as short and clean as possible and delegate to methods. This improves readability and ease of method navigation with an IDE.

The delegation can be with lambdas or method references.

Example of delegation using a lambda:

Java
source@Override
public Receive<Command> createReceive() {
  return newReceiveBuilder()
      .onMessage(Increment.class, notUsed -> onIncrement())
      .build();
}

private Behavior<Command> onIncrement() {
  count++;
  getContext().getLog().debug("[{}] Incremented counter to [{}]", name, count);
  return this;
}

When possible it’s preferred to use method references instead of lambdas. The benefit is less verbosity and in some cases it can actually give better type inference.

Java
source@Override
public Receive<Command> createReceive() {
  return newReceiveBuilder()
      .onMessage(GetValue.class, this::onGetValue)
      .build();
}

private Behavior<Command> onGetValue(GetValue command) {
  command.replyTo.tell(new Value(count));
  return this;
}

this::onGetValue is a method reference in above example. It corresponds to command -> onGetValue(command).

If you are using IntelliJ IDEA it has support for converting lambdas to method references.

More important than the choice between lambdas or method references is to avoid lambdas with a large block of code. An anti-pattern would be to inline all message handling inside the lambdas like this:

Java
source// this is an anti-pattern, don't use lambdas with a large block of code
  public Receive<Command> createReceive() {
    return newReceiveBuilder()
        .onMessage(
            Increment.class,
            notUsed -> {
              count++;
              getContext().getLog().debug("[{}] Incremented counter to [{}]", name, count);
              return this;
            })
        .onMessage(
            Tick.class,
            notUsed -> {
              count++;
              getContext()
                  .getLog()
                  .debug("[{}] Incremented counter by background tick to [{}]", name, count);
              return this;
            })
        .onMessage(
            GetValue.class,
            command -> {
              command.replyTo.tell(new Value(count));
              return this;
            })
        .build();
  }

In a real application it would often be more than 3 lines for each message. It’s not only making it more difficult to get an overview of the message matching, but compiler errors related to lambdas can sometimes be difficult to understand.

Ideally, lambdas should be written in one line of code. Two lines can be ok, but three is probably too much. Also, don’t use braces and return statements in one-line lambda bodies.

Partial versus total Function

It’s recommended to use a sealed trait as the super type of the commands (incoming messages) of an actor as the compiler will emit a warning if a message type is forgotten in the pattern match.

Scala
sourcesealed trait Command
case object Down extends Command
final case class GetValue(replyTo: ActorRef[Value]) extends Command
final case class Value(n: Int)

That is the main reason for Behaviors.receive, Behaviors.receiveMessage taking a Function rather than a PartialFunction.

The compiler warning if GetValue is not handled would be:

[warn] ... Counter.scala:45:34: match may not be exhaustive.
[warn] It would fail on the following input: GetValue(_)
[warn]         Behaviors.receiveMessage {
[warn]                                  ^

Note that a MatchError will be thrown at runtime if a message is not handled, so it’s important to pay attention to those. If a Behavior should not handle certain messages you can still include them in the pattern match and return Behaviors.unhandled.

Scala
sourceval zero: Behavior[Command] = {
  Behaviors.receiveMessage {
    case GetValue(replyTo) =>
      replyTo ! Value(0)
      Behaviors.same
    case Down =>
      Behaviors.unhandled
  }
}

It’s recommended to use the sealed trait and total functions with exhaustiveness check to detect mistakes of forgetting to handle some messages. Sometimes that can be inconvenient and then you can use a PartialFunction with Behaviors.receivePartial or Behaviors.receiveMessagePartial

Scala
sourceval zero: Behavior[Command] = {
  Behaviors.receiveMessagePartial {
    case GetValue(replyTo) =>
      replyTo ! Value(0)
      Behaviors.same
  }
}

How to compose Partial Functions

Following up from previous section, there are times when one might want to combine different PartialFunctions into one Behavior.

A good use case for composing two or more PartialFunctions is when there is a bit of behavior that repeats across different states of the Actor. Below, you can find a simplified example for this use case.

The Command definition is still highly recommended be kept within a sealed Trait:

Scala
sourcesealed trait Command
case object Down extends Command
final case class GetValue(replyTo: ActorRef[Value]) extends Command
final case class Value(n: Int)

In this particular case, the Behavior that is repeating over is the one in charge to handle the GetValue Command, as it behaves the same regardless of the Actor’s internal state. Instead of defining the specific handlers as a Behavior, we can define them as a PartialFunction:

Scala
sourcedef getHandler(value: Int): PartialFunction[Command, Behavior[Command]] = {
  case GetValue(replyTo) =>
    replyTo ! Value(value)
    Behaviors.same
}
def setHandlerNotZero(value: Int): PartialFunction[Command, Behavior[Command]] = {
  case Down =>
    if (value == 1)
      zero
    else
      nonZero(value - 1)
}
def setHandlerZero(log: Logger): PartialFunction[Command, Behavior[Command]] = {
  case Down =>
    log.error("Counter is already at zero!")
    Behaviors.same
}

Finally, we can go on defining the two different behaviors for this specific actor. For each Behavior we would go and concatenate all needed PartialFunction instances with orElse to finally apply the command to the resulting one:

Scala
sourceval zero: Behavior[Command] = Behaviors.setup { context =>
  Behaviors.receiveMessagePartial(getHandler(0).orElse(setHandlerZero(context.log)))
}

def nonZero(capacity: Int): Behavior[Command] =
  Behaviors.receiveMessagePartial(getHandler(capacity).orElse(setHandlerNotZero(capacity)))

// Default Initial Behavior for this actor
def apply(initialCapacity: Int): Behavior[Command] = nonZero(initialCapacity)

Even though in this particular example we could use receiveMessage as we cover all cases, we use receiveMessagePartial instead to cover potential future unhandled message cases.

ask versus ?

When using the AskPattern it’s recommended to use the ask method rather than the infix ? operator, like so:

Scala
sourceimport akka.actor.typed.scaladsl.AskPattern._
import akka.util.Timeout

implicit val timeout: Timeout = Timeout(3.seconds)
val counter: ActorRef[Command] = ???

val result: Future[OperationResult] = counter.ask(replyTo => Increment(delta = 2, replyTo))

You may also use the more terse placeholder syntax _ instead of replyTo:

Scala
sourceval result2: Future[OperationResult] = counter.ask(Increment(delta = 2, _))

However, using the infix operator ? with the placeholder syntax _, like is done in the following example, won’t typecheck because of the binding scope rules for wildcard parameters:

Scala
source// doesn't compile
val result3: Future[OperationResult] = counter ? Increment(delta = 2, _)

Adding the necessary parentheses (as shown below) makes it typecheck, but, subjectively, it’s rather ugly so the recommendation is to use ask.

Scala
sourceval result3: Future[OperationResult] = counter ? (Increment(delta = 2, _))

Note that AskPattern is only intended for request-response interaction from outside an actor. If the requester is inside an actor, prefer ActorContext.ask as it provides better thread-safety by not requiring the use of a FutureCompletionStage inside the actor.

ReceiveBuilder

Using the ReceiveBuilder is the typical, and recommended, way of defining message handlers, but it can be good to know that it’s optional in case you would prefer a different approach. Alternatives could be like:

  • direct processing because there is only one message type
  • pattern matching (Java 21 documentation)
  • if or switch statements
  • annotation processor

In Behaviors there are receive, receiveMessage and receiveSignal factory methods that takes functions instead of using the ReceiveBuilder, which is the receive with the class parameter.

In AbstractBehavior you can return your own akka.actor.typed.javadsl.Receive from createReceive instead of using newReceiveBuilder. Implement the receiveMessage and receiveSignal in the Receive subclass.

Nesting setup

When an actor behavior needs more than one of setup, withTimers and withStash the methods can be nested to access the needed dependencies:

Scala
sourcedef apply(): Behavior[Command] =
  Behaviors.setup[Command](context =>
    Behaviors.withStash(100)(stash =>
      Behaviors.withTimers { timers =>
        context.log.debug("Starting up")

        // behavior using context, stash and timers ...
      }))
Java
sourcepublic static Behavior<Command> apply() {
  return Behaviors.setup(
      context ->
          Behaviors.withStash(
              100,
              stash ->
                  Behaviors.withTimers(
                      timers -> {
                        context.getLog().debug("Starting up");

                        // behavior using context, stash and timers ...
                      })));
}

The order of the nesting does not change the behavior as long as there is no additional logic in any other function than the innermost one. It can be nice to default to put setup outermost as that is the least likely block that will be removed if the actor logic changes.

Note that adding supervise to the mix is different as it will restart the behavior it wraps, but not the behavior around itself:

Scala
sourcedef apply(): Behavior[Command] =
  Behaviors.setup { context =>
    // only run on initial actor start, not on crash-restart
    context.log.info("Starting")

    Behaviors
      .supervise(Behaviors.withStash[Command](100) { stash =>
        // every time the actor crashes and restarts a new stash is created (previous stash is lost)
        context.log.debug("Starting up with stash")
        // Behaviors.receiveMessage { ... }
      })
      .onFailure[RuntimeException](SupervisorStrategy.restart)
  }
Java
sourcepublic static Behavior<Command> create() {
  return Behaviors.setup(
      context -> {
        // only run on initial actor start, not on crash-restart
        context.getLog().info("Starting");

        return Behaviors.<Command>supervise(
                Behaviors.withStash(
                    100,
                    stash -> {
                      // every time the actor crashes and restarts a new stash is created
                      // (previous stash is lost)
                      context.getLog().debug("Starting up with stash");
                      // Behaviors.receiveMessage { ... }
                    }))
            .onFailure(RuntimeException.class, SupervisorStrategy.restart());
      });
}

Additional naming conventions

Some naming conventions have already been mentioned in the context of other recommendations, but here is a list of additional conventions:

  • replyTo is the typical name for the ActorRef[Reply]ActorRef<Reply> parameter in messages to which a reply or acknowledgement should be sent.

  • Incoming messages to an actor are typically called commands, and therefore the super type of all messages that an actor can handle is typically sealed trait Commandinterface Command {}.

  • Use past tense for the events persisted by an EventSourcedBehavior since those represent facts that have happened, for example Incremented.

Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.