Exception Handling

Exceptions thrown during route execution bubble up through the route structure to the next enclosing handleExceptions directive or the top of your route structure.

Similarly to the way that Rejections are handled the handleExceptions directive delegates the actual job of converting an exception to its argument, an ExceptionHandler, which is defined like this:

trait ExceptionHandler extends PartialFunction[Throwable, Route]

Since an ExceptionHandler is a partial function it can choose, which exceptions it would like to handle and which not. Unhandled exceptions will simply continue to bubble up in the route structure. At the root of the route tree any still unhandled exception will be dealt with by the top-level handler which always handles all exceptions.

Route.seal internally wraps its argument route with the handleExceptions directive in order to “catch” and handle any exception.

So, if you’d like to customize the way certain exceptions are handled you need to write a custom ExceptionHandler. Once you have defined your custom ExceptionHandler you have two options for “activating” it:

  1. Bring it into implicit scope at the top-level.
  2. Supply it as argument to the handleExceptions directive.

In the first case your handler will be “sealed” (which means that it will receive the default handler as a fallback for all cases your handler doesn’t handle itself) and used for all exceptions that are not handled within the route structure itself.

The second case allows you to restrict the applicability of your handler to certain branches of your route structure.

Here is an example for wiring up a custom handler via handleExceptions:

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._

val myExceptionHandler = ExceptionHandler {
  case _: ArithmeticException =>
    extractUri { uri =>
      println(s"Request to $uri could not be handled normally")
      complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
    }
}

object MyApp extends App {
  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()

  val route: Route =
    handleExceptions(myExceptionHandler) {
      // ... some route structure
      null // hide
    }

  Http().bindAndHandle(route, "localhost", 8080)
}

And this is how to do it implicitly:

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._

implicit def myExceptionHandler: ExceptionHandler =
  ExceptionHandler {
    case _: ArithmeticException =>
      extractUri { uri =>
        println(s"Request to $uri could not be handled normally")
        complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
      }
  }

object MyApp extends App {
  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()

  val route: Route =
  // ... some route structure
    null // hide

  Http().bindAndHandle(route, "localhost", 8080)
}

Default Exception Handler

A default ExceptionHandler is used if no custom instance is provided.

It will handle every NonFatal throwable, write its stack trace and complete the request with InternalServerError (500) status code.

The message body will contain a string obtained via Throwable#getMessage call on the exception caught.

In case getMessage returns null (which is true for e.g. NullPointerException instances), the class name and a remark about the message being null are included in the response body.

Note that IllegalRequestExceptions’ stack traces are not logged, since instances of this class normally contain enough information to provide a useful error message.

Note

Users are strongly encouraged not to rely on the ExceptionHandler as a means of handling ‘normally exceptional’ situations.

Exceptions are known to have a negative performance impact for cases when the depth of the call stack is significant (stack trace construction cost) and when the handler is located far from the place of the throwable instantiation (stack unwinding costs).

In a typical Akka application both these conditions are frequently true, so as a rule of thumb, you should try to minimize the number of Throwable instances reaching the exception handler.

To understand the performance implications of (mis-)using exceptions, have a read at this excellent post by A. Shipilёv: The Exceptional Performance of Lil’ Exception.

The source code for this page can be found here.