Coexistence

Dependency

To use Akka Actor Typed, you must add the following dependency in your project:

sbt
libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % "2.5.32"
Maven
<dependencies>
  <dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-actor-typed_2.12</artifactId>
    <version>2.5.32</version>
  </dependency>
</dependencies>
Gradle
dependencies {
  implementation "com.typesafe.akka:akka-actor-typed_2.12:2.5.32"
}

Introduction

We believe Akka Typed will be adopted in existing systems gradually and therefore it’s important to be able to use typed and untyped actors together, within the same ActorSystem. Also, we will not be able to integrate with all existing modules in one big bang release and that is another reason for why these two ways of writing actors must be able to coexist.

There are two different ActorSystems: akka.actor.ActorSystem and akka.actor.typed.ActorSystem.

Currently the typed actor system is implemented using an untyped actor system under the hood. This may change in the future.

Typed and untyped can interact the following ways:

  • untyped actor systems can create typed actors
  • typed actors can send messages to untyped actors, and opposite
  • spawn and supervise typed child from untyped parent, and opposite
  • watch typed from untyped, and opposite
  • untyped actor system can be converted to a typed actor system

In the examples the akka.actor package is aliased to untyped.

Scala
sourceimport akka.{ actor => untyped }

The examples use fully qualified class names for the untyped classes to distinguish between typed and untyped classes with the same name.

Untyped to typed

While coexisting your application will likely still have an untyped ActorSystem. This can be converted to a typed ActorSystem so that new code and migrated parts don’t rely on the untyped system:

Scala
source// adds support for typed actors to an untyped actor system and context
import akka.actor.typed.scaladsl.adapter._

val system = akka.actor.ActorSystem("UntypedToTypedSystem")
val typedSystem: ActorSystem[Nothing] = system.toTyped
Java
source// In java use the static methods on Adapter to convert from typed to untyped
import akka.actor.typed.javadsl.Adapter;
akka.actor.ActorSystem untypedActorSystem = akka.actor.ActorSystem.create();
ActorSystem<Void> typedActorSystem = Adapter.toTyped(untypedActorSystem);

Then for new typed actors here’s how you create, watch and send messages to it from an untyped actor.

Scala
sourceobject Typed {
  sealed trait Command
  final case class Ping(replyTo: ActorRef[Pong.type]) extends Command
  case object Pong

  val behavior: Behavior[Command] =
    Behaviors.receive { (context, message) =>
      message match {
        case Ping(replyTo) =>
          context.log.info(s"${context.self} got Ping from $replyTo")
          // replyTo is an untyped actor that has been converted for coexistence
          replyTo ! Pong
          Behaviors.same
      }
    }
}
Java
sourcepublic abstract static class Typed {
  interface Command {}

  public static class Ping implements Command {
    public final akka.actor.typed.ActorRef<Pong> replyTo;

    public Ping(ActorRef<Pong> replyTo) {
      this.replyTo = replyTo;
    }
  }

  public static class Pong {}

  public static Behavior<Command> behavior() {
    return Behaviors.receive(Typed.Command.class)
        .onMessage(
            Typed.Ping.class,
            (context, message) -> {
              message.replyTo.tell(new Pong());
              return same();
            })
        .build();
  }
}

The top level untyped actor is created in the usual way:

Scala
sourceval untypedActor = system.actorOf(Untyped.props())
Java
sourceakka.actor.ActorSystem as = akka.actor.ActorSystem.create();
akka.actor.ActorRef untyped = as.actorOf(Untyped.props());

Then it can create a typed actor, watch it, and send a message to it:

Scala
sourceclass Untyped extends untyped.Actor with ActorLogging {
  // context.spawn is an implicit extension method
  val second: ActorRef[Typed.Command] =
    context.spawn(Typed.behavior, "second")

  // context.watch is an implicit extension method
  context.watch(second)

  // self can be used as the `replyTo` parameter here because
  // there is an implicit conversion from akka.actor.ActorRef to
  // akka.actor.typed.ActorRef
  second ! Typed.Ping(self)

  override def receive = {
    case Typed.Pong =>
      log.info(s"$self got Pong from ${sender()}")
      // context.stop is an implicit extension method
      context.stop(second)
    case untyped.Terminated(ref) =>
      log.info(s"$self observed termination of $ref")
      context.stop(self)
  }
}
Java
sourcepublic static class Untyped extends AbstractActor {
  public static akka.actor.Props props() {
    return akka.actor.Props.create(Untyped.class);
  }

  private final akka.actor.typed.ActorRef<Typed.Command> second =
      Adapter.spawn(getContext(), Typed.behavior(), "second");

  @Override
  public void preStart() {
    Adapter.watch(getContext(), second);
    second.tell(new Typed.Ping(Adapter.toTyped(getSelf())));
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
        .match(
            Typed.Pong.class,
            message -> {
              Adapter.stop(getContext(), second);
            })
        .match(
            akka.actor.Terminated.class,
            t -> {
              getContext().stop(getSelf());
            })
        .build();
  }
}

There is one import that is needed to make that work. We import the Adapter class and call static methods for conversion.

Scala
source// adds support for typed actors to an untyped actor system and context
import akka.actor.typed.scaladsl.adapter._
Java
source// In java use the static methods on Adapter to convert from typed to untyped
import akka.actor.typed.javadsl.Adapter;

That adds some implicit extension methods that are added to untyped and typed ActorSystem and ActorContext in both directions. To convert between typed and untyped there are adapter methods in akka.actor.typed.javadsl.Adapter. Note the inline comments in the example above.

Typed to untyped

Let’s turn the example upside down and first start the typed actor and then the untyped as a child.

The following will show how to create, watch and send messages back and forth from a typed actor to this untyped actor:

Scala
sourceobject Untyped {
  def props(): untyped.Props = untyped.Props(new Untyped)
}
class Untyped extends untyped.Actor {
  override def receive = {
    case Typed.Ping(replyTo) =>
      replyTo ! Typed.Pong
  }
}
Java
sourcepublic static class Untyped extends AbstractActor {
  public static akka.actor.Props props() {
    return akka.actor.Props.create(Untyped.class);
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
        .match(
            Typed.Ping.class,
            message -> {
              message.replyTo.tell(new Typed.Pong());
            })
        .build();
  }
}

Creating the actor system and the typed actor:

Scala
sourceval system = untyped.ActorSystem("TypedWatchingUntyped")
val typed = system.spawn(Typed.behavior, "Typed")
Java
sourceActorSystem as = ActorSystem.create();
ActorRef<Typed.Command> typed = Adapter.spawn(as, Typed.behavior(), "Typed");

Then the typed actor creates the untyped actor, watches it and sends and receives a response:

Scala
sourceobject Typed {
  final case class Ping(replyTo: akka.actor.typed.ActorRef[Pong.type])
  sealed trait Command
  case object Pong extends Command

  val behavior: Behavior[Command] =
    Behaviors.setup { context =>
      // context.actorOf is an implicit extension method
      val untyped = context.actorOf(Untyped.props(), "second")

      // context.watch is an implicit extension method
      context.watch(untyped)

      // illustrating how to pass sender, toClassic is an implicit extension method
      untyped.tell(Typed.Ping(context.self), context.self.toClassic)

      Behaviors
        .receivePartial[Command] {
          case (context, Pong) =>
            // it's not possible to get the sender, that must be sent in message
            // context.stop is an implicit extension method
            context.stop(untyped)
            Behaviors.same
        }
        .receiveSignal {
          case (_, akka.actor.typed.Terminated(_)) =>
            Behaviors.stopped
        }
    }
}
Java
sourcepublic abstract static class Typed {
  public static class Ping {
    public final akka.actor.typed.ActorRef<Pong> replyTo;

    public Ping(ActorRef<Pong> replyTo) {
      this.replyTo = replyTo;
    }
  }

  interface Command {}

  public static class Pong implements Command {}

  public static Behavior<Command> behavior() {
    return akka.actor.typed.javadsl.Behaviors.setup(
        context -> {
          akka.actor.ActorRef second = Adapter.actorOf(context, Untyped.props(), "second");

          Adapter.watch(context, second);

          second.tell(
              new Typed.Ping(context.getSelf().narrow()), Adapter.toClassic(context.getSelf()));

          return akka.actor.typed.javadsl.Behaviors.receive(Typed.Command.class)
              .onMessage(
                  Typed.Pong.class,
                  (_ctx, message) -> {
                    Adapter.stop(context, second);
                    return same();
                  })
              .onSignal(akka.actor.typed.Terminated.class, (_ctx, sig) -> stopped())
              .build();
        });
  }
}

Supervision

The default supervision for untyped actors is to restart whereas for typed it is to stop. When combining untyped and typed actors the default supervision is based on the default behavior of the child i.e. if an untyped actor creates a typed child, its default supervision will be to stop. If a typed actor creates an untyped child, its default supervision will be to restart.

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.