Akka Actors … in Java!

Alejandro Picetti
Globant
Published in
5 min readNov 15, 2023
Photo by Erik Mclean on Unsplash

Background: the Akka Framework

Akka is a concurrency framework based on the Actors’ concurrency model, described back in 1973 by Carl Hewitt. It’s used in Scala programming language to create reactive, scalable software.

What is an Actor?

An actor implements the Active Object OO pattern, that is, an object with its own executing thread. An actor receives messages from its context, either the main code or another actor, and reacts by executing code that handles the message and can send messages to other actors, including the one that sent the message. As a side note, this model is like the original OOP implemented in Smalltalk but multi-threaded instead). For a more detailed definition, see here.

So, a program is a system of actors interacting among them. To use actors in a software program, we need to create an Actor System, which we will use to instantiate our actors and manage their lifecycle. The Actor System is a heavyweight object, so the software must create only one object of this type.

Is Akka a framework only for Scala?

Although Akka itself is written in Scala, the good news is that it’s not a framework only for this language. It provides a Java API that Java applications can consume. This article aims to get our feet wet in Akka actors with a simple HelloWorld-like example.

Example: the Greeter

We will implement the Greeter, an actor who will receive two possible messages:

  • SayHello
  • SayGoodbye

Both messages are similar in that they will contain the name to which they say “hello” or “goodbye”. The Greeter actor will just provide the wording for the greeting. Our main program will be the sender and, hence, the receiver of the response messages; particularly, when the response is a “goodbye,” it will close the Actor System and quit.

We will use brand new Java 21 LTS for this implementation since it adds records and pattern matching for objects in switch expressions; these features will be very useful in message handling inside the Greeter actor by making the code more Scala-like.

First, we’ll create a new Maven project specifying Java 21:

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

We’ll add the Akka Actor library as a dependency (version 2.8.4):

<dependencies>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.12</artifactId>
<version>2.8.4</version>
</dependency>
</dependencies>

This would be the basic project’s source code structure (POM file omitted):

<project-root-dir>
|
+-- src
|
+-- java
|
+-- com.example.akkajava
|
+-- Greeter.java
|
+-- Main.java
...
...

Now, we can implement the Greeter actor (Greeter.java). Note the numbered comments, which will be described in more detail after the code:

package org.example.akkajava;

import akka.actor.UntypedAbstractActor;

import java.util.logging.Logger;

public class Greeter extends UntypedAbstractActor { // 1

private Logger logger = Logger.getLogger(this.getClass().getName());

// 2
public record SayHello(String name) {}
public record SayGoodbye(String name) {}
public record Greeting(String text, boolean isGoodbye) {}

// 3
@Override
public void onReceive(Object message) throws Throwable {
switch (message) { // 4
case SayHello hello -> {
logger.info("Say Hello");
getContext().getSender().tell( // 5
new Greeting(
String.format("Hello, %s!", hello.name),
false
),
getSelf()
);
}
case SayGoodbye bye -> {
logger.info("Say Goodbye");
getContext().getSender().tell(
new Greeting(
String.format("Goodbye, %s!", bye.name),
true
),
getSelf()
);
}
default -> logger.info("What?");
}
}
}
  • 1: The class that defines our actor extends UntypedAbstractActor. This class is the base for untyped actors, actors that do not receive messages of a given class or its subclasses but can receive messages of any class/type.
  • 2: We define the actor’s communication protocol, the set of classes that model the messages that our actor can receive or send. In our case, we use Java 21’s records to model them.
  • 3: We need to implement the onReceive() method from UntypedAbstractActor.
  • 4: We determine which message type the actor received by using pattern matching on objects in a switch expression (remember that Java 21 provides this feature, which unclutters code).
  • 5: We send the response message back to the received message’s sender, which we get from the actor’s context. Here, we use the tell() method, which implements the fire-and-forget messaging pattern: send the message and do not wait for a response; in this case, we are sending back a response for a received message, so we don’t expect a response to our response message here.

Once our actor is defined, we now use it on our main program (Main.java). It will communicate with the Greeter using the request/response ( or ask ) messaging pattern, which asynchronously waits for a response from the actor it sent the message to (for more detail on fire-and-forget and ask patterns, see here). Also here, note the numbered comments described after the code:

package org.example.akkajava;

import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.dispatch.OnComplete;
import akka.util.Timeout;
import scala.concurrent.Await;

import java.time.Duration;
import java.util.logging.Level;
import java.util.logging.Logger;

import static akka.pattern.Patterns.ask;
import static scala.concurrent.duration.Duration.*;

public class Main {

private static Logger logger = Logger.getLogger(Main.class.getName());

// 1
private static final ActorSystem system = ActorSystem.apply("test");

// 2
private static final Timeout ASK_TIMEOUT =
Timeout.create(Duration.ofMillis(2000L)); //(2)

// 3
private static final OnComplete<Object> ON_COMPLET_HANDLER =
new OnComplete<>() {
@Override
public void onComplete(Throwable failure, Object success) {
if (failure != null) {
logger.log(Level.SEVERE, "Response error", failure);
} else {
if (success instanceof Greeter.Greeting greeting) {
logger.info(greeting.text());
if (greeting.isGoodbye()) {
system.terminate();
}
} else {
logger.info("What???");
}
}
}
};

public static void main(String[] args) throws Exception {
var greeter = system.actorOf(Props.create(Greeter.class)); // 4
ask(greeter, new Greeter.SayHello("Alejandro"), ASK_TIMEOUT) // 5
.onComplete(ON_COMPLET_HANDLER, system.dispatcher());
ask(greeter, "Hi, there!", ASK_TIMEOUT) // 6
.onComplete(ON_COMPLET_HANDLER, system.dispatcher());
ask(greeter, new Greeter.SayGoodbye("Alejandro"), ASK_TIMEOUT)
.onComplete(ON_COMPLET_HANDLER, system.dispatcher());
Await.ready(system.whenTerminated(), Inf()); // 7
}
}
  • 1: We create the Actor System that will create and manage all actors and their lifecycle (in our case, just one)
  • 2: We define a maximum timeout for the request/response, setting it to 2 seconds.
  • 3: This is the definition of the response handler for the request/response
  • 4: We use the Actor System to create a new Greeter actor.
  • 5: The method ask() sends a message to the Greeter actor and sets the response handler for the response message.
  • 6: Let’s try an invalid message type.
  • 7: Upon receiving a “goodbye” response, our response handler will shut the entire Actor System down. After sending both messages, our main program will wait for the Actor System to be completely shut down before ending.

If we did nothing wrong, we should be able to build and run it a couple of times. This is the sample output from my first run:

Sample output from first program run.

And this is the sample from my second run. Note that messages arrive to the actor in the same order that we sent them, but responses do not necessarily return in the same order.

The second sample output shows a different response order

That’s it! We have successfully implemented our first actor in Java using Akka!

Summary

In this article, we had a quick introduction to the Actors concurrency model. We got our feet wet in the Akka actors framework in Java by looking at a simple example using just one actor. This example doesn’t show much about concurrency, so I’ll cover a more complex example in an upcoming article. See you there!

--

--