Why Akka uses Exceptions rather than Either and Option?

As part of my master thesis, I have studied Akka (especially Akka Typed).
I still need to clarify why Akka throws Exceptions instead of returning Either or Option.
Akka is all about fault tolerance, and, in my view, the approach of using Either and Option is more robust than that of Exceptions.
Let me make a coupe of examples:

If an actor spawns two child actors with the same name, it throws InvalidActorNameException.
package thesis;

import akka.actor.typed.*;
import akka.actor.typed.javadsl.Behaviors;

public class NonUnique {
    public static Behavior<Void> create() {
        return Behaviors.setup(ctx -> {
            ActorRef<Void> child1 = ctx.spawn(Behaviors.empty(), "non-unique");
            ActorRef<Void> child2 = ctx.spawn(Behaviors.empty(), "non-unique");

            return Behaviors.empty();
        });
    }

    public static void main(String[] args) {
        final ActorSystem<Void> system = ActorSystem.create(create(), "helloakka");
    }
}
If an the actor has invalid initial behavior, it throws ActorInitializationException.
package thesis;

import akka.actor.typed.*;
import akka.actor.typed.javadsl.Behaviors;

public class InvalidInitialBehavior {
    public static Behavior<Void> create() {
        return Behaviors.setup(ctx -> {
            ActorRef<Void> child1 = ctx.spawn(Behaviors.same(), "invalid-initial-behavior");

            return Behaviors.empty();
        });
    }

    public static void main(String[] args) {
        final ActorSystem<Void> system = ActorSystem.create(create(), "helloakka");
    }
}

I am mainly a Rust programmer, and I am accustomed to returning a Result with Err variant or an Option in operations that can fail. Scala simulates Result with Either .
Why Akka decided for the Exception approach?
Is it maybe because Akka also wants to target Java users, who aren’t very familiar with Either?

2 Likes

For actors, like the example you shared, a core design in Akka is supervision, where unexpected failures - exception thrown when actors start or handle a message, is handled by a supervision strategy. This is not expected to be used for errors like for example validating user input. You can read a bit more about that in the Fault Tolerance Section of the docs.

There is some extra overhead in allocating a wrapping ADT (so Some, Try or Either) which also needs to happen for successful cases (each object will take at least 16 bytes of heap on a 64bit JVM), so for example in actor message handling or in Akka Stream .map you would not want to add such overhead for every successful operation.

In some use cases it makes sense and is ok with the overhead to always wrap and users can choose to represent for example Akka Stream elements as potentially failed using some appropriate data structure.

One could potentially argue this overhead matters for actor start as well - in your example it would not make any difference, but in a design where short lived actors are spawned at a high frequency it could definitely start adding up.

In user facing APIs on the Scala side where it makes sense we use various ADTs for representing failures, see for example Context.ask, pipeToSelf, StatusReplies. This is also inherently built into any dealing with Scala futures as the result of a future is a Scala scala.util.Try which corresponds to a Result.

Nowadays there is Optional on the Java side of things, but there isn’t really a standard library data structure for representing failures-or-success there other than throwing, so that is also a valid point, especially since your sample snippets were Java.

2 Likes