scalabeginner

Exception Handling Patterns

Handle exceptions in Scala: try-catch-finally, Try monad, NonFatal, and custom exceptions.

scala
import scala.util.{Try, Success, Failure, Using}
import scala.util.control.NonFatal

// Custom exceptions
class AppException(message: String, cause: Throwable = null)
    extends RuntimeException(message, cause)

class NotFoundException(entity: String, id: String)
    extends AppException(s"$entity not found: $id")

class ValidationException(field: String, reason: String)
    extends AppException(s"Validation failed for $field: $reason")

// Basic try-catch-finally
def parseNumber(s: String): Int =
  try
    s.toInt
  catch
    case e: NumberFormatException =>
      println(s"Cannot parse '$s': ${e.getMessage}")
      0
  finally
    println(s"Attempted to parse: $s")

// Using Try monad
def safeDivide(a: Double, b: Double): Try[Double] =
  Try(a / b).filter(d => !d.isInfinite && !d.isNaN)

def loadConfig(path: String): Try[Map[String, String]] = Try {
  val source = scala.io.Source.fromFile(path)
  try
    source.getLines()
      .filter(_.contains("="))
      .map { line =>
        val Array(k, v) = line.split("=", 2)
        k.trim -> v.trim
      }
      .toMap
  finally
    source.close()
}

// Chaining Try operations
def processData(input: String): Try[String] =
  for
    num    <- Try(input.toInt)
    result <- safeDivide(100.0, num)
    output <- Try(f"Result: $result%.4f")
  yield output

// NonFatal matcher (catches everything except fatal errors)
def safeExecute[T](block: => T): Either[String, T] =
  try Right(block)
  catch case NonFatal(e) => Left(s"${e.getClass.getSimpleName}: ${e.getMessage}")

// Retry pattern
def withRetry[T](maxAttempts: Int, delay: Long = 100)(block: => T): Try[T] =
  var lastError: Throwable = null
  for attempt <- 1 to maxAttempts do
    try return Success(block)
    catch
      case NonFatal(e) =>
        lastError = e
        println(s"Attempt $attempt/$maxAttempts failed: ${e.getMessage}")
        if attempt < maxAttempts then Thread.sleep(delay)
  Failure(lastError)

@main def run(): Unit =
  // Basic
  println(s"Parse '42': ${parseNumber("42")}")
  println(s"Parse 'abc': ${parseNumber("abc")}")
  println()

  // Try monad
  println(s"10/3: ${safeDivide(10, 3)}")
  println(s"10/0: ${safeDivide(10, 0)}")

  // Chain
  println(processData("4"))
  println(processData("0"))
  println(processData("abc"))

  // Recovery
  val recovered = processData("abc").recover {
    case _: NumberFormatException => "Default: 0"
  }
  println(s"Recovered: $recovered")

  // Pattern matching on Try
  processData("5") match
    case Success(v) => println(s"Success: $v")
    case Failure(e) => println(s"Failed: ${e.getMessage}")

  // NonFatal
  println(safeExecute(42 / 0))
  println(safeExecute("hello".toInt))

  // Custom exceptions
  try
    throw NotFoundException("User", "42")
  catch
    case e: NotFoundException => println(s"Caught: ${e.getMessage}")
    case e: AppException => println(s"App error: ${e.getMessage}")

Use Cases

  • Graceful error recovery
  • File and network error handling
  • Retry logic for flaky operations

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.