Scala - String to Int/Double Conversion
Scala's `String` class provides `toInt` and `toDouble` methods for direct conversion. These methods throw `NumberFormatException` if the string cannot be parsed.
Key Insights
- Scala provides multiple conversion methods including
toInt,toDouble, and the saferTrywrapper to handle parsing exceptions gracefully - The
Optionpattern combined withtoIntOption/toDoubleOption(Scala 2.13+) offers idiomatic null-safe conversions without exception handling overhead - For production code, prefer explicit error handling with
Eitheror validation libraries like Cats to provide meaningful failure context rather than swallowing exceptions
Basic String to Int Conversion
Scala’s String class provides toInt and toDouble methods for direct conversion. These methods throw NumberFormatException if the string cannot be parsed.
val numStr = "42"
val num: Int = numStr.toInt
println(num) // 42
val priceStr = "99.99"
val price: Double = priceStr.toDouble
println(price) // 99.99
This approach works for valid numeric strings but crashes on invalid input:
val invalid = "abc"
val result = invalid.toInt // throws NumberFormatException
Use this method only when you’re certain the input is valid, such as when parsing hardcoded configuration values or after validation.
Safe Conversion with Try
The scala.util.Try wrapper catches exceptions and converts them into Success or Failure instances. This provides explicit error handling without try-catch blocks.
import scala.util.{Try, Success, Failure}
def parseIntSafe(s: String): Try[Int] = Try(s.toInt)
parseIntSafe("123") match {
case Success(value) => println(s"Parsed: $value")
case Failure(exception) => println(s"Failed: ${exception.getMessage}")
}
// Using map and getOrElse for default values
val result = Try("456".toInt).getOrElse(0)
println(result) // 456
val resultInvalid = Try("xyz".toInt).getOrElse(0)
println(resultInvalid) // 0
Try is particularly useful when chaining operations:
def calculateTotal(priceStr: String, quantityStr: String): Try[Double] = {
for {
price <- Try(priceStr.toDouble)
quantity <- Try(quantityStr.toInt)
} yield price * quantity
}
calculateTotal("29.99", "3") match {
case Success(total) => println(f"Total: $$${total}%.2f")
case Failure(_) => println("Invalid input")
}
// Output: Total: $89.97
Option-Based Conversion (Scala 2.13+)
Scala 2.13 introduced toIntOption and toDoubleOption methods that return Option[Int] and Option[Double] respectively. These methods provide cleaner syntax for optional values without exception overhead.
val maybeInt: Option[Int] = "42".toIntOption
println(maybeInt) // Some(42)
val maybeInvalid: Option[Int] = "not-a-number".toIntOption
println(maybeInvalid) // None
// Pattern matching
"123".toIntOption match {
case Some(value) => println(s"Got $value")
case None => println("Invalid number")
}
// Using map and getOrElse
val doubled = "10".toIntOption.map(_ * 2).getOrElse(0)
println(doubled) // 20
For Double conversions with Option:
def calculateDiscount(priceStr: String, discountStr: String): Option[Double] = {
for {
price <- priceStr.toDoubleOption
discount <- discountStr.toDoubleOption
if discount >= 0 && discount <= 100
} yield price * (1 - discount / 100)
}
println(calculateDiscount("100.0", "20")) // Some(80.0)
println(calculateDiscount("100.0", "150")) // None (invalid discount)
Handling Multiple Conversions with Either
For applications requiring detailed error messages, Either provides more context than Option or Try. By convention, Left contains errors and Right contains successful values.
def parseIntEither(s: String): Either[String, Int] = {
try {
Right(s.toInt)
} catch {
case _: NumberFormatException => Left(s"'$s' is not a valid integer")
}
}
def parseDoubleEither(s: String): Either[String, Double] = {
try {
Right(s.toDouble)
} catch {
case _: NumberFormatException => Left(s"'$s' is not a valid double")
}
}
parseIntEither("42") match {
case Right(value) => println(s"Success: $value")
case Left(error) => println(s"Error: $error")
}
// Output: Success: 42
parseIntEither("abc") match {
case Right(value) => println(s"Success: $value")
case Left(error) => println(s"Error: $error")
}
// Output: Error: 'abc' is not a valid integer
Combining multiple Either operations:
def processOrder(idStr: String, amountStr: String): Either[String, (Int, Double)] = {
for {
id <- parseIntEither(idStr)
amount <- parseDoubleEither(amountStr)
validAmount <- if (amount > 0) Right(amount)
else Left("Amount must be positive")
} yield (id, validAmount)
}
processOrder("1001", "250.50") match {
case Right((id, amount)) => println(s"Order $id: $$${amount}")
case Left(error) => println(s"Invalid order: $error")
}
// Output: Order 1001: $250.5
Custom Conversion with Validation
For complex validation requirements, create custom parsers with business logic:
sealed trait ParseError
case class InvalidFormat(input: String) extends ParseError
case class OutOfRange(value: Int, min: Int, max: Int) extends ParseError
def parseAge(s: String): Either[ParseError, Int] = {
Try(s.toInt) match {
case Success(age) if age >= 0 && age <= 150 => Right(age)
case Success(age) => Left(OutOfRange(age, 0, 150))
case Failure(_) => Left(InvalidFormat(s))
}
}
parseAge("25") match {
case Right(age) => println(s"Valid age: $age")
case Left(InvalidFormat(input)) => println(s"'$input' is not a number")
case Left(OutOfRange(value, min, max)) =>
println(s"$value is outside valid range $min-$max")
}
Performance Considerations
When parsing large datasets, consider using Java’s parsing methods directly for better performance:
import java.lang.{Integer, Double => JDouble}
def fastParseInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s))
} catch {
case _: NumberFormatException => None
}
}
def fastParseDouble(s: String): Option[Double] = {
try {
Some(JDouble.parseDouble(s))
} catch {
case _: NumberFormatException => None
}
}
// Benchmark-friendly batch processing
def parseIntList(strings: List[String]): List[Int] = {
strings.flatMap(s => Try(s.toInt).toOption)
}
val numbers = List("1", "2", "invalid", "4", "5")
println(parseIntList(numbers)) // List(1, 2, 4, 5)
Working with Different Number Bases
Parse hexadecimal, octal, or binary strings using Java’s Integer.parseInt with radix:
def parseHex(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s, 16))
} catch {
case _: NumberFormatException => None
}
}
println(parseHex("FF")) // Some(255)
println(parseHex("1A3")) // Some(419)
def parseBinary(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s, 2))
} catch {
case _: NumberFormatException => None
}
}
println(parseBinary("1010")) // Some(10)
println(parseBinary("11111111")) // Some(255)
Choose conversion methods based on your error handling requirements: toInt/toDouble for guaranteed valid input, Option for simple presence/absence semantics, Try for exception wrapping, and Either for detailed error reporting. In production systems, prefer explicit error handling over exception throwing to maintain predictable control flow.