Akka Typed (with Java)

  • Post author:
  • Post category:Akka / Java

This post is a followup to “Akka Typed (with Scala)” to demonstrate how to use Akka Typed with Java.

What is Akka?

Akka is a framework for writing message driven, scalable, error resilient applications, while making working with concurrency easy. This is achieved by modeling applications around the concept of “actors”.

Actors are entities that communicate with each other through messages. When an actor receives a message, it can react by

  • changing its internal state,
  • creating new actors, and
  • sending out messages to actors.

Multiple actors typically run in parallel, but the business logic within each actor is always executed single threaded, thereby avoiding complications that normally arise from concurrency.

Akka Typed

However, until the advent of Akka Typed, messages passed between actors have been untyped: You have been able to send any kind of message to any actor without the compiler being able to assert if the receiving actor is able to handle the message. This prevented detecting errors during compilation and made reasoning about actor driven applications difficult at times.

With Akka Typed, you can now specify which message types an actor is able to handle and the compiler will complain about mismatching message types sent to an actor.

An Example

The following is an example that shows the new Akka typed API in action (you can view and download the complete source code here, including both the Scala and Java code):

package dev.oreineke;

import akka.actor.typed.ActorSystem;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.Behaviors;

public class Main {
    interface Message {}
    static class Greet implements Message {
        String name;

        Greet(String name) {
            this.name = name;
        }
    }
    static class Stop implements Message {}

    public static void main(String[] args) {
        Behavior<Message> readyToGreet = Behaviors.receive(Message.class)
                .onMessage(Greet.class, (context, message) -> {
                    context.getLog().info("Hello " + message.name + "!!!");
                    return Behaviors.same();
                })
                .onMessage((Stop.class), (context, message) -> {
                    context.getLog().info("Goodbye!");
                    return Behaviors.stopped();
                })
                .build();

        ActorSystem<Message> greeter = ActorSystem.create(readyToGreet, "Greeter");

        greeter.tell(new Greet("Peter"));
        greeter.tell(new Greet("Paul"));
        greeter.tell(new Greet("Marry"));
        greeter.tell(new Stop());
    }
}

Explanation

After defining the message type the actor is willing to accept in an interface Message,

    interface Message {}
    static class Greet implements Message {
        String name;

        Greet(String name) {
            this.name = name;
        }
    }
    static class Stop implements Message {}

a Behavior<Message> named readyToGreet is defined:

        Behavior<Message> readyToGreet = Behaviors.receive(Message.class)
                .onMessage(Greet.class, (context, message) -> {
                    context.getLog().info("Hello " + message.name + "!!!");
                    return Behaviors.same();
                })
                .onMessage((Stop.class), (context, message) -> {
                    context.getLog().info("Goodbye!");
                    return Behaviors.stopped();
                })
                .build();

Note that, to define the actor behavior readyToGreet, a typed factory method Behaviors.receive is used. It expects a function mapping the received message of type Message to a new Behavior. For the returned Behavior, instead of defining new behaviors, predefined factory methods are used to generate special return values: Behaviors.same to cause the current behavior to stay active, and Behavior.stop to cause the actor to die.

Finally, ActorSystem.create() is called to create an actor with the Behavior readyToGreet (along with a name for easy actor identification):

        ActorSystem<Message> greeter = ActorSystem.create(readyToGreet, "Greeter");

At this point, the actor greeter is ready to receive massages of type Message:

        greeter.tell(new Greet("Peter"));
        greeter.tell(new Greet("Paul"));
        greeter.tell(new Greet("Marry"));
        greeter.tell(new Stop());

If we would have tried to send a message of type String to the actor, the compiler would have noticed the error:

  greeter.tell("Eat this"); // this will fail during compilation