Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: best way to handle exceptions from Java library?

Tags:

Let's say I'm using a Java library from my Scala project. That Java library throws exceptions all over the place, but I don't feel comfortable just letting them propagate "in the Scala world", since there's no way to be sure what exceptions a Scala method can throw (except by documenting them). So this is the kind of code I tend to write:

def doesNotThrowExceptions(parameter: String): Either[Throwable, T] =   catching(classOf[IOException], classOf[NoSuchAlgorithmException]) either {     // Code calling the Java library     // Code generating a value of type T   } 

Then, typically, I'll use Either.RightProjection.flatMap to chain methods that return Either[Throwable, ...] or Either.RightProjection.map to mix methods that return Either[Throwable, ...] and other methods. Or simply Either.fold to do something with the Throwable value. Somehow, though, this still doesn't feel totally right.

Is this the most "idiomatic" way to deal with Java's exceptions in Scala? Isn't there a better way?

like image 227
Olivier Bruchez Avatar asked Feb 15 '12 10:02

Olivier Bruchez


People also ask

How do you handle exceptions in Scala?

try/catch/finally A basic way we can handle exceptions in Scala is the try/catch/finally construct, really similar to the Java one. In the following example, to make testing easier, we'll return a different negative error code for each exception caught: def tryCatch(a: Int, b: Int): Int = { try { return Calculator.

Where should I handle exceptions Java?

The try-catch is the simplest method of handling exceptions. Put the code you want to run in the try block, and any Java exceptions that the code throws are caught by one or more catch blocks. This method will catch any type of Java exceptions that get thrown. This is the simplest mechanism for handling exceptions.

Why does Scala have no checked exceptions?

However, unlike Java, Scala has no “checked” exceptions—you never have to declare that a function or method might throw an exception. In Java, “checked” exceptions are checked at compile time. If your method might throw an IOException, you must declare it.


2 Answers

I'm not sure there's a single most idiomatic way to deal with Java's exceptions because there are at least four different cases they'll get thrown:

  1. Something that neither you nor the library designer really expected to go wrong went wrong.
  2. Something that the library designer hoped would work didn't, and you need to know the details.
  3. Something you hoped would work didn't, and you don't need to know the details.
  4. Sometimes the method produces a value and sometimes not, and it communicates that by throwing an exception.

Best practices arguably differ for each of these cases

1. Truly exceptional exceptions

Scala has fully-featured exception handling. There is nothing wrong with letting an unanticipated exception propagate uncaught until you're at a level where you can do something about it. Packaging every possible exception into an Either can waste a lot of time. Just document what you know you're not handling, and use try/catch at an appropriately high level (e.g. a saveEverything method should probably go in a try/catch block (or wrap its contents in one) because no matter what went wrong, if saving everything failed you probably want to attempt to salvage the situation, not just die).

In particular, you probably want to handle Error this way and only package Exception, not all Throwables, into Either.

2. Exceptions that you need to know about

This is the case you're talking about, and you've already given several good suggestions for how to deal with them. As you've already noticed, you can use catching to package the exceptions into Either. You then can also

a. Use pattern matching, which will let you pull apart your Either in more depth:

doesNotThrowExceptions("par").right.map(transformData) match {   case Left(ioe: IOException) => /* ... */   case Left(nsae: NoSuchAlgorithmException) => /* ... */   case Right(x) => /* ... */   case Left(e) => throw e  // Didn't expect this one... } 

b. Downconvert to Option after logging the error:

doesNotThrowExceptions("par").left.map{ e =>   println("You're not going to like this, but something bad happened:")   println(e)   println("Let's see if we can still make this work....") }.right.toOption 

c. If exceptions are a really important part of your flow-control, Either may not be enough. You might instead want to define your own Either-like class with more than just Left and Right. Or you can nest the left side of the Either:

try { Right(/* java code */) } catch {   case ioe: IOException => Left(Left(ioe))   case nsae: NoSuchAlgorithmException => Left(Right(nsae)) } 

d. Use Scalaz Validation, which is a lot like Either but is tailored a little more to exception handling.

3. Exceptions where you only need to know something went wrong, and

4. Exceptions thrown to indicate no-return-value

Even though these are conceptually two different categories, you handle them the same way:

catching(classOf[IOException], classOf[NoSuchAlgorithmException]) opt { ... } 

to get an Option back. Then map, flatMap, etc..

like image 84
Rex Kerr Avatar answered Sep 27 '22 21:09

Rex Kerr


I always prefer scalaz.Validation over scala.Either. The two are algebraically identical structures, but the former is functionally richer (has more useful methods and type class instances).

Check this link for a great presentation by Chris Marshall on scalaz.Validation and other Scalaz goodies.

In order to talk to the code that uses classic exception handling mechanism, I have defined the following enriching methods and a type class instance:

implicit def catchW[A](underlying: Catch[A]) = new CatchW(underlying)  class CatchW[A](underlying: Catch[A]) {   def validation(body: => A): Validation[Throwable, A] = {     underlying.withApply(_.fail)(body.success)   }    def vnel(body: => A): ValidationNEL[Throwable, A] = {     underlying.withApply(_.failNel)(body.success)   } }  implicit def catchMonoid[A] = new Monoid[Catch[A]] {   val zero = noCatch   def append(x: Catch[A], y: => Catch[A]) = x or y } 
like image 27
missingfaktor Avatar answered Sep 27 '22 20:09

missingfaktor