Scala Try Type
Try[A] wraps a computation that might throw an exception. Instead of using try-catch everywhere, you call Try { ... } and get back either a Success(value) or a Failure(exception). This turns exception-prone code into a value that you can transform, chain, and handle functionally.
The Structure
Try[A]
│
├── Success(value: A) → computation succeeded
│
└── Failure(exception: Throwable) → computation threw an exception
Creating a Try
import scala.util.{Try, Success, Failure}
val good = Try(10 / 2) // Success(5)
val bad = Try(10 / 0) // Failure(ArithmeticException: / by zero)
val parse = Try("42".toInt) // Success(42)
val fail = Try("abc".toInt) // Failure(NumberFormatException: ...)
println(good) // Success(5)
println(bad) // Failure(java.lang.ArithmeticException: / by zero)
Pattern Matching on Try
def safeDivide(a: Int, b: Int): Try[Double] =
Try(a.toDouble / b)
safeDivide(10, 4) match
case Success(result) => println(f"Result: $result%.2f")
case Failure(ex) => println(s"Error: ${ex.getMessage}")
// Result: 2.50
safeDivide(10, 0) match
case Success(result) => println(f"Result: $result%.2f")
case Failure(ex) => println(s"Error: ${ex.getMessage}")
// Error: / by zero
map and flatMap
val result = Try("123")
.map(_.toInt)
.map(_ * 2)
.map(n => s"Double: $n")
println(result) // Success(Double: 246)
val failure = Try("abc")
.map(_.toInt) // Failure here
.map(_ * 2) // skipped
.map(n => s"$n") // skipped
println(failure) // Failure(NumberFormatException: ...)
recover and recoverWith
val safe = Try("not-a-number".toInt)
.recover {
case _: NumberFormatException => 0 // fallback value
}
println(safe) // Success(0)
// recoverWith returns another Try
val safe2 = Try("not-a-number".toInt)
.recoverWith {
case _: NumberFormatException => Try("999".toInt)
}
println(safe2) // Success(999)
For Comprehension with Try
def parseDouble(s: String): Try[Double] = Try(s.toDouble)
def safeSqrt(n: Double): Try[Double] =
if n >= 0 then Success(Math.sqrt(n))
else Failure(new IllegalArgumentException("Negative number"))
val computation =
for
n <- parseDouble("16.0")
root <- safeSqrt(n)
yield f"√$n%.1f = $root%.1f"
println(computation) // Success(√16.0 = 4.0)
val bad =
for
n <- parseDouble("abc") // Failure here
root <- safeSqrt(n) // skipped
yield root
println(bad) // Failure(NumberFormatException: ...)
Converting Try to Other Types
val t1 = Try(42)
val t2 = Try(throw new RuntimeException("oops"))
t1.toOption // Some(42)
t2.toOption // None
t1.toEither // Right(42)
t2.toEither // Left(RuntimeException: oops)
t1.getOrElse(-1) // 42
t2.getOrElse(-1) // -1
Wrapping External Calls
import scala.io.Source
def readFile(path: String): Try[String] =
Try(Source.fromFile(path).mkString)
def parseJson(content: String): Try[Map[String, String]] =
Try(Map("key" -> content.take(20))) // simplified
val result = readFile("config.json").flatMap(parseJson)
result match
case Success(config) => println(s"Loaded config: $config")
case Failure(ex) => println(s"Failed to load: ${ex.getMessage}")
Try vs Either vs Option
Situation Use
────────────────────────────── ─────────
Code that throws exceptions Try
Failure with a custom message Either
Value may simply be missing Option
Practical: Chained Safe Operations
case class Config(host: String, port: Int, timeout: Int)
def parseConfig(raw: Map[String, String]): Try[Config] =
for
host <- Try(raw("host"))
portStr <- Try(raw("port"))
port <- Try(portStr.toInt)
timeStr <- Try(raw("timeout"))
timeout <- Try(timeStr.toInt)
yield Config(host, port, timeout)
val good = Map("host" -> "localhost", "port" -> "5432", "timeout" -> "30")
val bad = Map("host" -> "localhost", "port" -> "NaN", "timeout" -> "30")
println(parseConfig(good)) // Success(Config(localhost,5432,30))
println(parseConfig(bad)) // Failure(NumberFormatException: For input string: "NaN")
