Stackable actor traits with Akka and Scala explained

Stackable actors seem to be very popular pattern when working with Akka. At some point many engineers realize that it would be nice to extract common receive behaviour into separate traits and compose them as needed. I’ve read couple blog posts about this technique but I feel none of them was really good at explaining how stackable actors work.

The idea

Lets assume our project consists of number of actors. There is nothing wrong about the entire platform, neither particular actor. As our project grows from time to time we have a need to add common functionality to all the actors, or specific subset of them. Let it be logging of received messages, or measuring time spent in receive method or anything else common.

Easiest solution would be add desired functionality to one of the actors and then copy/paste it. Slightly improved version would be to create parent class for our actors and put all the common features in there. Child actors would just have to make sure they call parent methods and proper time. Problem is this parent class quickly turns into a kitchen sink? And you can’t really trust that dozens of child actors will use parent in proper way, always.

Ideally it would be nice to implement actors as usual and just mix in behaviours we are interested in. Something like:

The usual. But how to make sure that all, or just specific, messages are also processed by mixed in traits? And at the same time how not to introduce new API that differs too much from the well known and established actor API?

Stackable actor

Introducing StackableActor. A trait that makes stacking (mixing) actors easy and at the same time does not introduce much different API.

How it works. The receive method checks if wrappedReceive is defined for a message and if so calls it. Otherwise it calls unhandled. It basically passes responsibility of handling messages to wrappedReceive.

wrappedReceive is a variable that holds current way of handling messages. That’s where our case statements will go. It’s a variable so that we can change behaviour of our actors on the fly by assigning to it different partial function to handle messages in different way.

wrappedBecome is our new context.become. It simply assigns new partial function to variable wrappedReceive. This way we can change behaviour of actor on the fly just like we would do it using context.become.

New actors

Implementation of our actors does not have to change much when using  stackable actors. There are only three things to learn:

  • do not define receive method, instead implement your own message handling method and give it meaningful name (it’s a good practice anyway)
  • somewhere in constructor of the actor call wrappedBecome to set initial behaviour of actor, passing to it the method mentioned in previous point; comparing regular actors you would assign your method to receive
  • whenever behaviour of actor needs to change instead of calling context.become, use wrappedBecome

Not much different than working with regular actors Instead of receive method we have our own method called initialReceive and we call wrappedBecome in the constructor passing initialReceive so that the actor can start handling messages. There is no need to call super.receive, no need to overwrite any specific abstract method. A lot of freedom to implement actors as we want.

Stackable traits

Our stackable traits require a bit more attention. The trait needs to overwrite receive method and must not forget to call super.receive. That’s the mechanism of stacking the traits. Particular trait will handle message of specific type (or any message) and will call super.receive to let next trait in the line have chance to handle it in its way.

The below example is a trait that for every message it receives it writes to a log an entry just before processing the message, and right after processing it.

But stackable trait does not have to wrap all received messages in exactly same logic. One can imagine a trait that wraps only some types of messages and transparently passes forward all other messages to the next trait on the stack. For example there might be HttpLogging trait that logs all received HTTP requests and passes forward other messages it might receive (eg. HttpConnectionClosed):

With a bit of bravery we can even manipulate received messages before they get processed by our actor. We might have a trait that adds unique request ID to our message, or tracing information, fixes some common and safe to fix message issues (like formatting). The point is the message we pass to super.receive does not have to be exactly the same message as the actor has received. However be careful manipulating messages this way. It’s too easy to obscure message flow or break something.

Putting it all together

Having couple stackable traits implemented we can keep our actors clean and just mix in common features. Without magic or tedious super.receive wiring. Lets take a look at the very first code snipped from the post converted into stackable actor.

When our actor receives a message it will be first handled by NiceLogging trait. This trait, beside wrapping interesting messages, needs to call super.receive for all messages it receives. This way our message can bubble to SomeMeasurement, which does the same. It intercepts some (or all) messages, wraps them, and forwards all messages by calling super.receive. Eventually we end up in last trait, for which super.receive is the receive from StackableActor. The one that tests wrappedReceive for being able to handle a message and if so using it, or treating the message as unhandled.


Stackable actor can simplify how we think about actors. It allows extracting common behaviour from actors and composing them with actors in more clean, manageable way.

There are few key differences between this implementation and some other examples I’ve found when learning how to stack actors:

  • receive of particular trait can have more than one statement to wrap different messages in different ways; it might be not obvious for people new to Akka; just remember to call super.receive in each case and to have case for handling “all other messages” as well
  • wrappedReceive is a variable (as opposed to, for example) abstract method
  • so that wrappedBecome is trivial to implement
  • and engineers working on particular actors have more decision freedom


Author: Wojciech Szela

CSM, Consultant, Manager, Entrepreneur and Open Source Contributor. Scrum and eXtreme Programming practitioner. Evangelist of healthy work environment, high quality products, people development and team work.