🎓 A1Learner
🔍
0 of 48 lessons completed

Scala Future Errors

A Future error is a failure held inside the Future instead of thrown at once. It stays inside until the program handles it.

Recover replaces a failure with a fallback

Dividing by zero fails inside the Future. recover turns that failure into -1 before Await.

import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

@main def main(): Unit =
  val f = Future { 10 / 0 }
  val safe = f.recover { case e: Exception => -1 }
  val result = Await.result(safe, 5.seconds)
  println(result)

Recover leaves a success unchanged

The Future holds 8 with no failure. recover has nothing to replace, so the awaited value stays 8.

import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

@main def main(): Unit =
  val f = Future { 8 }
  val safe = f.recover { case e: Exception => -1 }
  val result = Await.result(safe, 5.seconds)
  println(result)

RecoverWith replaces a failure with a new Future

The failing Future is replaced by a successful Future holding 99. Awaiting the replacement gives 99.

import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

@main def main(): Unit =
  val f = Future { 10 / 0 }
  val safe = f.recoverWith { case e: Exception => Future.successful(99) }
  val result = Await.result(safe, 5.seconds)
  println(result)

Example

import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

@main def main(): Unit =
  val good = Future { 20 }
  val risky = Future { 10 / 0 }
  val safe = risky.recover { case e: Exception => 0 }
  val total = Await.result(good, 5.seconds) + Await.result(safe, 5.seconds)
  println(total)

Key points

  • Failure: a Future whose work throws holds the failure instead of stopping the program.
  • Recover: f.recover { case e: Exception => -1 } replaces a failure with a fallback value.
  • RecoverWith: f.recoverWith { case e: Exception => Future.successful(0) } replaces a failure with a new Future.
  • Success: recover leaves a successful Future unchanged.
  • Order: recover before Await, so Await never meets an unhandled failure.

In this example, the good Future holds 20 and the risky Future fails on division by zero. recover turns the failure into 0 before Await, so the printed total is 20.

Note

Await.result on a failed Future throws the failure. That is why the failure is recovered first.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Scastie

Exercise

What happens if you Await a failed Future without recovering it first?