A Java 8 API for Akka



Due to Java syntax limitations, the Java API built over the Scala implementation was, at least, clumsy. With version 2.3.0 (on March 2014) Akka introduced a Java 8 API, called lambda support. More recently (Akka 2.4.2, on February 2016) they started replacing scala.concurrent.Future with java.util.concurrent.CompletableFuture .

Let’s walk through some of the improvements that are making Akka much more Java developer friendly.

AbstractActor and receive message loop

Actors may now extend AbstractActor , and implement message handling logic in a receive(...) block, defined inside the constructor.

// New Java 8 API version public class Barista extends AbstractLoggingActor { private Barista() { final CoffeeMachine coffeeMachine = new CoffeeMachine(); log().info("The coffee machine is ready"); receive( match(Order.class, order -> { log().info("Received an order for {}", order); final Coffee coffee = coffeeMachine.prepare(order.type); log().info("Your {} is ready", coffee); sender().tell(coffee, self()); }) .matchAny(this::unhandled) .build() ); } public static Props props() { return Props.create(Barista.class, () -> new Barista()); } }

This clearly mimics Scala pattern matching. It pushes Java syntax to the limit (I still cannot convince my IntelliJ to format the receive block properly…), to create a sort of DSL, but the result is pretty clear. Much, much better than the verbosity of the pre-Java8 interface:

// Old Java API version public class OldBarista extends UntypedActor { private LoggingAdapter log = Logging.getLogger(getContext().system(), this); private final CoffeeMachine coffeeMachine; @Override public void onReceive(Object message) throws Exception { if (message instanceof Order) { Order order = (Order) message; log.info("Received an order for {}", order); Coffee coffee = coffeeMachine.prepare(order.type); log.info("Your {} is ready", coffee); getSender().tell(coffee, getSelf()); } else { unhandled(message); } } public static Props props() { return Props.create(new Creator() { private static final long serialVersionUID = 1L; @Override public OldBarista create() throws Exception { return new OldBarista(); } }); } private OldBarista() { coffeeMachine = new CoffeeMachine(); log.info("The coffee machine is ready"); } }

In my example above, I extended AbstractLoggingActor , instead of AbstractActor , just to avoid creating the LoggingAdapter .

Behaviour HotSwap: become and unbecome

To change the message handling behaviour dynamically you have to create one or more PartialFunction defining different behaviours. The syntax is no different from defining the main behaviour in a receive(...) block:

// New Java 8 API private PartialFunction behaviour = match(Order.class, order -> { // ... process order ... })

The former interface forced you to create an anonymous class, with the usual clutter: