In this blog we will look at how to write your first actor program – create new actors and send messages between them.

This blog is part of the tutorial series – Learn Akka, in which we will learn about using Akka with Java.

Table of Contents

1.0 How to create Actors and send messages.

In this section we will learn following

  • How to create actors
  • How to send messages to actors

Note: We have created a Gradle based project in Java, and added following dependency to build.gradle

compile group: 'com.typesafe.akka', name: 'akka-actor_2.12', version: '2.5.20'

 

1.1 Define the message format

Actors communicate via messages. Before we can define the actor class, we need to define the format of messages that will be used for communication.

The message can be any simple Java Bean.

For example a message class could be as simple as one below

@Data
@AllArgsConstructor
public final class DemoMessage {
    private String message;
}

In our current example it only has a String attribute. But it can have any number and types of attributes.

We have used Lombok, for avoid writing all the boiler plate code for constructors, getter/setters, etc.

1.2 Creating the actor class

All actor classes needs to extend from akka.actor.AbstractActor class.

However in most of our examples we will extend the class akka.actor.AbstractLoggingActor, since it adds the logging functionality to the our actor classes.

We can use methods like log().info() / log().debug() / log().warn(), etc in our actor classes to log information.

 

We will need to implement following method from AbstractActor.scala in our actor class

def createReceive(): AbstractActor.Receive

createReceive method has no arguments and returns AbstractActor.Receive. It defines which messages your Actor can handle, along with the implementation of how the messages should be processed.

 

This is how your actor class should look like

public class TestActor extends AbstractLoggingActor {

    @Override
    public Receive createReceive() {
        // Actual code here to handle the message
    }
}

 

Now comes the question what to write in the createRecieve() method and how to return the Recieve object back?

In Java, we can use the class akka.japi.pf.ReceiveBuilder, to build the behavior of createReceive() method and return a Receive object.

  • Create a ReceiveBuilder object, in either of the following way
    • ReceiveBuilder.create() OR
    • AbstractActor.receiveBuilder()
  • Call the match() method to match the type of message you want to handle in createRecieve()
  • Finally call the build() method, to create the Receive object from the builder.

Lets look at a complete code now on how a Actor looks like.

public class TestActor extends AbstractLoggingActor {

    @Override
    public Receive createReceive() {

        return receiveBuilder()
            .match(MessageOne.class, msg -> {
                // If message is type of MessageOne.class, then execute this code
                // Some code
            })
            .match(MessageTwo.class, msg -> {
               // If message is type of MessageTwo.class, then execute this code    
               // Some code
            }).build();
    }
}

Top ∆

1.3 Adding Props to Actor classes

The Props class contains the actor configuration.
This class is immutable, thus thread-safe, so it can be shared when creating new actors.

When we instantiate the actor class, we need to pass Props object to it.

We can configure lot of things in the Props, like the dispatcher, the mailbox or deployment configuration. Don’t worry about these components for now, as we will cover them in future sections.

It is not mandatory to write the Props creation method inside the actor class.

However it’s highly recommended and considered a best-practice to define the factory methods inside the actor object that will handle the creation of the Props object.

Below is an example of adding a static method in the Actor to handle creation of Props

public class TestActor extends AbstractLoggingActor {

    static Props props() {
        return Props.create(TestActor.class, TestActor::new);
    }
    
    @Override
    public Receive createReceive() {
       // ....
       // some code
    }
}

In above example we are providing following information to the Props.create()

  • Class – TestActor.class
  • How to instantiate the class – TestActor::new

 

In case our actor class has some constructor arguments, we can also pass those to the Props. Consider following example

public class TestActor extends AbstractLoggingActor {

    private int initValue;

    public TestActor(int initValue) {
        this.initValue = initValue;
    }

    static Props props(int initValue) {
        return Props.create(TestActor.class, initValue);
    }

    @Override
    public Receive createReceive() {
       // ....  
       // some code        
    }
}

The use of the static factory method to create Props, will become more clear when we instantiate an Actor.

 

Top ∆

1.4 Instantiating an actor

Now that we have seen how to create an actor, lets instantiate an actor.

To create an actor, we first need an Actor System.

An Actor System is a hierarchical group of actors which share common configuration, e.g. dispatchers, deployments, remote capabilities and addresses.

So we need to create an Actor System first. It can be done in the following way.

ActorSystem actorSystem = ActorSystem.create("demo");

 

Actors can be created using the actorOf() method

This method can be called from two places

  • From Actor System – it creates root level actors
    ActorRef parentActor = actorSystem.actorOf (TestActor.props(), "testActor");
  • From Context of current actors – creates child actors
    ActorRef childActor = getContext().actorOf (ChildActor.props(), "child01");

getContext() gives information and methods relating to the context within which the actor is running.

Above examples assume that we have a factory method props(), defined in the actor classes.

 

 

1.5 Sending messages to an actor (tell vs forward)

Actors can be sent messages using tell() method on ActorRef

e.g.

ActorRef actorOne = actorSystem.actorOf(ActorOne.props(), "actorOne");

DemoMessage msg = new DemoMessage("Hello World!!");

actorOne.tell(msg, ActorRef.noSender());

Above code sends a message to actorOne.

The second parameter specifies who the sender is. This is useful when the actor receiving the message needs to send a response to an actor (sender of the message).

Some possible values are

  • ActorRef.noSender() – equivalent to no sender actor for this method
  • self() – send the reference of the current actor
  • Additionally we can also send reference of any other actor (e.g parent). This is used in case we want a specific actor to handle the responses from the actor recieving the message.

 

We also have the forward() method which is similar to tell().  The difference is that the original sender of the message is kept when sending the message, so the actor forwarding the message only acts as an intermediary actor.

 

Top ∆

2.0 Writing the first Akka Actor Program

We will try to simulate following actor behavior.

actors-first-program

2.1 Create the Message class

@Data
@AllArgsConstructor
public final class DemoMessage {
    private String message;
}

 

@Data
@AllArgsConstructor
public final class AckMessage {
    private String message;
}

 

2.2 Create the First Actor Class – Actor One

 

public class ActorOne extends AbstractLoggingActor {

    static Props props() {
        return Props.create(ActorOne.class, ActorOne::new);
    }

    @Override
    public Receive createReceive() {

        return receiveBuilder()
            .match(DemoMessage.class, msg -> {

                System.out.println(getSelf() + " : Message processed : " + msg.getMessage());

                ActorRef actorTwo = getContext().actorOf(ActorTwo.props(), "actorTwo");

                actorTwo.tell(new DemoMessage("Welcome to Akka...."), self());

            })
            .match(AckMessage.class, msg -> {

                System.out.println(getSelf() + " : Acknowledge Recieved : " + msg.getMessage());

            }).build();
    }
}

 

 

2.3 Creating the second actor – ActorTwo

public class ActorTwo extends AbstractLoggingActor {

    static Props props() {
        return Props.create(ActorTwo.class, ActorTwo::new);
    }

    @Override
    public Receive createReceive() {

        return ReceiveBuilder.create()
            .match(DemoMessage.class, msg -> {

                System.out.println(getSelf() + " : Message processed : " + msg.getMessage());

                sender().tell(new AckMessage("I am done !!!"), ActorRef.noSender());

            }).build();
    }
}

 

 

2.4 Tying it all together – Running the program

 

public class FirstAkkaDemo {


    public static void main(String[] args) {

        ActorSystem actorSystem = ActorSystem.create("demo");

        ActorRef actorOne = actorSystem.actorOf(ActorOne.props(), "actorOne");

        actorOne.tell(new DemoMessage("Hello World!!"), ActorRef.noSender());
    }
}

 

 

2.5 Output of the Program

Actor[akka://demo/user/actorOne#-550210128] : Message processed : Hello World!!
Actor[akka://demo/user/actorOne/actorTwo#1652871501] : Message processed : Welcome to Akka....
Actor[akka://demo/user/actorOne#-550210128] : Acknowledge Recieved : I am done !!!

 

Top ∆


In the next blog of the Learn Akka tutorial series, we will look at Actors – Components and Lifecycle.