Two exceptions "at the same time", what is the proper way to handle this situation?

Viewed 55

I have a situation where I need to call to foo method, if it fails call bar method, then re-throw the original exception (wrapped).

My problem is that bar method could fail too, and its exception information is important too.

When I create a new Exception there only one cause, and I have two. I could create something like class MyException(cause1, cause2) extends RuntimeException(cause1) but cause2 will be out of the standard mechanisms that exceptions have (stacktrace, etc).

Is there a better way to handle this situation?

The following code is a simplified example.

// I can't change this code
def foo:String = throw new FooException("foo method fails")
def bar:String = throw new BarException("bar method fails")

// my code
try {
  foo
} catch {
  case error1:FooException =>
    try {
      // if foo fails, I want to call bar
      bar
      throw new MyException("my exception", error1)
    } catch {
      case error2:BarException =>
        // but bar could fail too
        throw new MyException("my exception", ???)
        // if my cause is error1, I lost error2 information (message and stack)
        // if my cause is error2, I lost error1 information (message and stack)
    }
}

Thank you in advance.

1 Answers

You can pack your MyException throwable with one or both of the "suppressed" exceptions.

val myEx = new MyException("my exception")
myEx.addSuppressed(error1)
myEx.addSuppressed(error2)
throw myEx

Then it's up to the catching code to unpack it.

case err : MyException =>
  val arr :Array[Throwable] = err.getSuppressed
  // arr contains all the "suppressed" exceptions that were added,
  // stack traces and all

Still, it's arguably better to express the possibility of error in the type system rather than leave it to the (perhaps nonexistent) catching code to do the right thing.

import util.Try

val res :Either[List[Throwable],String] =
  Try(foo).fold(fErr =>
    Try(bar).fold(bErr => Left(fErr::bErr::Nil), _ => Left(fErr::Nil))
  , Right(_))

From there you can proceed as needed.

res match {
  case Right(s)  => println(s)  //foo result string
  case Left(lst) =>
    println(lst.map(_.getMessage()).mkString(",")) //foo method fails,bar method fails
    lst.foreach(_.printStackTrace())               //both stack traces
}
Related