Custom Exception in scala

In order to reflect all the original constructors from Exception I'd implement a custom exception with the following pattern:

class CustomException(msg: String) extends Exception(msg) {
  def this(msg: String, cause: Throwable) = {
    this(msg)
    initCause(cause)
  }

  def this(cause: Throwable) = {
    this(Option(cause).map(_.toString).orNull)
    initCause(cause)
  }

  def this() = {
    this(null: String)
  }
}

This can be also achieved with a trait as mentioned in previous answer. I'd just not create individual classes in this case:

trait SomeException { self: Throwable =>
  def someDetail: SomeDetail
}

then, when throwing:

throw new Exception(...) with SomeException {
  override val someDetail = ...
}

and when matching:

try {
  ...
} catch {
  case ex: Throwable with SomeException =>
    ex.getCause
    ex.getMessage
    ex.someDetail
}

The advantage here is that you are not sticking to any particular constructor of the parent exception.

something more or less like that.


final case class CustomException(private val message: String = "", 
                           private val cause: Throwable = None.orNull)
                      extends Exception(message, cause) 

Just try catch:

try {
    throw CustomException("optional")
} catch {
    case c: CustomException =>
          c.printStackTrace
}

class MyException(message: String) extends Exception(message) {

  def this(message: String, cause: Throwable) {
    this(message)
    initCause(cause)
  }

  def this(cause: Throwable) {
    this(Option(cause).map(_.toString).orNull, cause)
  }

  def this() {
    this(null: String)
  }
}

This is almost identical to @Jacek L.'s answer. I just wanted to add some more input on the motive behind this answer.

Why so many constructors?

Throwable is written in kind of a funny way. It has 4 constructors -- ignoring the one with the boolean toggles -- each of them behaves a bit differently with nulls, and these differences could only be maintained with multiple constructors.

It would have been a bit cleaner if Scala would have allowed to call a superclass constructor via super, but it doesn't :(

Why not a case class?

  • Perfectly maintaining the constructors' behavior regarding nulls wouldn't be possible; specifically, both def this() and def this(message: String) will have to set the cause to null, while originally it is set to this.
  • toString will not be overridden.
  • The message and the cause are already publicly available via getMessage and getCause. Adding another reference to these is redundant.
  • equals will be overridden and will behave differently.
    Meaning, new Exception("m") == new Exception("m") // false
    while new CaseException("m") == new CaseException("m") // true

If one desires to access the message and the cause via pattern-matching, one can simply implement the unapply method:

object MyException {
  def unapply(e: MyException): Option[(String,Throwable)] = Some((e.getMessage, e.getCause))
}

You might want to create a sealed trait:

sealed trait MyException {
  // This is called a "self annotation". You can use "self" or "dog" or whatever you want.
  // It requires that those who extend this trait must also extend Throwable, or a subclass of it.
  self: Throwable =>
  val message: String
  val details: JsValue
}

Then you can have as many case classes as you need extending not only Exception, but your new trait.

case class CustomException(message: String) extends Exception(message) with MyException {
  override val details: JsValue = Json.obj("message" -> message, "etc" -> "Anything else")
}

Now, the whole point of using Scala is walking towards a more functional programming style, it will make your app more concurrent, so if you need to use your new custom exception, you might want to try something like this:

  def myExampleMethod(s: Option[String]): Future[Boolean] = Try {
    s match {
      case Some(text) =>
        text.length compareTo 5 match {
          case 1 => true
          case _ => false
        }
      case _ => throw CustomException("Was expecting some text")
    }
  }
  match {
    case Success(bool) => Future.successful(bool)
    case Failure(e) => Future.failed(e)
  }