Scala – using Option can still result in a NPE

trunk_1_lg

Two interesting scenarios where you can get a NullPointerException you didn’t expect when using Options in Scala. (Usually involves interfacing with Java libraries.)

You’re not supposed to be able to encounter a NullPointerException while using Scala Options. This is true when all of the code you are interacting with returns an Option instead of a null.

It can even be true when you wrap all values that could be null in an Option. “Option(null)” automatically becomes None.

But it is not true under two scenarios that are unfortunately very easy to re-create.

 


 

Example #1
REPL output of Some(null) causing an NPE

scala> :paste
// Entering paste mode (ctrl-D to finish)

class CustomerName(val firstName: String, val lastName: String)
class Customer(val name: CustomerName)
class Item(itemName: String, itemCost: String)
class ShipmentBox(orderNum: String, cust: Customer, items: List[Item]) {
    require(orderNum != null)
    val orderNumber = orderNum
    val customer = Some(cust)
    val itemsInBox = Some(items)
}
val mikesFirstOrderBox1 = new ShipmentBox("A100", null, null)

// Exiting paste mode, now interpreting.

defined class CustomerName
defined class Customer
defined class Item
defined class ShipmentBox
mikesFirstOrderBox1: ShipmentBox = ShipmentBox@943a2d0

scala> mikesFirstOrderBox1.customer.get.name
java.lang.NullPointerException
  ... 33 elided

As you can see from this REPL output, someone created a new ShipmentBox and supplied “null” for the customer. Whoever implemented “ShipmentBox” probably thought that “Some(cust)” would turn the null into a None. But only “Option(cust)” would have done that. The result is the NullPointerException in the final attempt to get the customer’s name.

As you will notice later, this first example and the second example share something in common: getting a value from an object held inside another object.

The way to avoid this first example from happening to you is to remember to use “Option()” not “Some()” when creating an option from any value that you aren’t 100% sure can’t be null. In other words, always use “Option()”.

Example 2
REPL Output of mapping out a hierarchy of objects where one value can return null

scala> :paste
// Entering paste mode (ctrl-D to finish)

class CustomerName(val firstName: String, val lastName: String)
class Customer(val name: CustomerName)
class Item(itemName: String, itemCost: String)
class ShipmentBox(orderNum: String, cust: Customer, items: List[Item]) {
    require(orderNum != null)
    val orderNumber = orderNum
    val customer = Option(cust)
    val itemsInBox = Option(items)
}

val guestCustomer = new Customer(new CustomerName("Guest","Customer"))
val mike = new Customer(new CustomerName("Mike", null))
val mikesFirstOrderBox1 = new ShipmentBox("A100", mike, null)

// Exiting paste mode, now interpreting.

defined class CustomerName
defined class Customer
defined class Item
defined class ShipmentBox
guestCustomer: Customer = Customer@7469d7f4
mike: Customer = Customer@50e10fe1
mikesFirstOrderBox1: ShipmentBox = ShipmentBox@67d9a642


scala> val mikesLastName = mikesFirstOrderBox1.customer.map(_.name).map(_.lastName).getOrElse("No last name") //null!? not what I expected...
mikesLastName: String = null

Here the null appears even though we have used “.getOrElse” and supplied a default value of “No last name.” This is because the value of “mikesFirstOrderBox1.customer.map(_.name).map(_.lastName)” is not “None” as we expected but “Some(null).” Believe it or not, “Some(null)” is actually a legitimate use case. Sometimes you want to know that a null value is there, for example when interfacing with some bad Java code.

But most of us probably want to eliminate all nulls like the vermin they are. One way to do this is to flatMap the value and explicitly wrap it in option as follows:

scala> val mikesLastName = mikesFirstOrderBox1.customer.flatMap(x => Option(x.name)).flatMap(x => Option(x.lastName)).getOrElse("No last name")
mikesLastName: String = No last name

Looks ugly, but it works.

Leave a Reply

Your email address will not be published. Required fields are marked *