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.
csharp// 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:
csharp// 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<Object,BoxedUnit>
defining different behaviours. The syntax is no different from defining the main behaviour in a receive(...)
block:
// New Java 8 API
private PartialFunction<Object, BoxedUnit> behaviour =
match(Order.class, order -> {
// ... process order ...
})
The former interface forced you to create an anonymous class, with the usual clutter:
// Old Java API
Procedure behaviour = new Procedure() { @Override public void apply(Object message) { if (message instanceof Order) { // …process order… } }};
Finite State Machine
FSM, is a frequently used pattern with Actors:State(S) x Event(E) -> Action(A), State(S’)…or, rewording Akka documentation:If an Actor is in state S and receives a message E, it should perform the action A and make a transition to state S’.The pre-Java 8 Java Actor interface has no support for FSM. You have to implement it by hand, applying a lot of discipline. The lambda-support provides a specialised AbstractFSM<State, Data>
superclass, with a quasi-DSL for matching combinations of Event (received message) and State. It also isolate creation and handling of a transition (goto(State)
, stay()
…) and the transition logic (onTransition(...)
). Better, extending AbstractLoggingFSM<State, Data>
gives you more than a pre-defined logger. Beside automatically logging all event and state transitions, it also keeps a rolling log of the last events, amazingly useful when debugging.Akka documentation contains a very simple example of FSM.
CompletableFuture
to replace Scala Future
Another recent important improvement is the gradual replacement of Scala Future
with proper Java 8 CompletableFuture
throughout the Java API. The authors of Akka were forced to leak Scala Standard Library elements into Java interface, to go beyond the Java Standard Library limits. As a Java developer, coping with scala.Future
is a hacky experience, regardless the Futures
helper class.For example, a new helper class PatternsCS
had been recently introduced, to implement the very useful ask pattern (asynchronous request/response), piping the asynchronous result to another actor without blocking, using CompletableFuture
. It replaces Patterns
that uses scala.concurrent.Future
.Consider you have a Java method (not an actor) returning and asynchronous result:
public CompletableFuture prepare(Coffee.CoffeeType type) { CompletableFuture eventualCoffee = new CompletableFuture<>(); // Good cooking takes time. If you are made to wait, it is to serve you better, and to please you. return eventualCoffee;}An actor using this method may pipe the result, after potentially adding more processing stages, with a clean syntax:receive( match(Order.class, order -> { pipe(coffeeMachine.prepare(order.type) .thenApply(coffee -> { // … additional preparation stages… return coffee; }), context().dispatcher()) .to(sender(), self()); }));
Still some Scala Future
around
Unfortunately, the migration to CompletableFuture
is not… complete, yet. There is still some Scala Future
around, remarkably in the CircuitBreaker
:receive( match(Order.class, order -> { scala.concurrent.Future response = circuitBreaker .callWithCircuitBreaker( () -> coffeeMachine.prepare(order.type) ); pipe( response, getContext().system().dispatcher() ).to( sender() ); }));Note that pipe(...)
method here is from akka.pattern.Patterns
, while in the previous example it was from the newer akka.pattern.PatternsCS
.[Updated after publishing]With a prompt reaction, Akka team has added support for CircuitBreakers using CompletableFuture
. It will be probably included in the next Akka release.Thanks, Konrad!
Code examples
Code examples used in this post (and a bit more): https://github.com/opencredo/akka-java8-examples
Wrapping up
Akka is moving to a proper Java 8 interface. In this article, I just skimmed some of the changes to the Java API. More have been introduced, radically changing the interface. For example, all the Akka Persistence event-sourcing support have been rewritten.As of today (Akka version 2.4.7), the migration is not complete. The lambda support is still marked as experimental, so you may still expect breaking changes even through minor releases. I think Lightbend (formerly Typesafe), the company governing Akka development, is steering toward Java and improvements of Akka Java API are a direct consequence. From a Java developer point of view, these improvements are making Akka much more friendly that it used to be.As a Java developer, you may still see Scala under the hood. Akka is written in Scala. Akka authors think in Scala, so the Java 8 interface looks very Scala-like. Some of the syntax choices appear quirky, to the eye of a Java developer.But, after all, this is useful. The more similar the two interface are, the easier is for Java developer to use the Akka community content, answer, blogposts, examples, mostly written in Scala.
This blog is written exclusively by the OpenCredo team. We do not accept external contributions.