Thursday, March 19, 2015

Optional in Java: Why it can shield (but not save) you from null

With the introduction of the Optional type in Java 8, there has been a lot of discussion about it's use and it's relation to the null problem (anything can potentially be null, aka the billion dollar mistake). In particular, the view of some is that adding Optional to Java without fixing the null problem doesn't save you from null pointer exceptions, adds complexity without providing sufficient benefit, and that it would have been better to provide safe navigation operators instead - my views are set out below.

What Optional is, and when to use it

In plain English, Optional means exactly that: "optional". It's used to designate, via the type system:
  • An input parameter which is not necessarily required, or
  • A return value or field not guaranteed to be present.
Previously null (which is effectively outside the type system), would have been used for this purpose, but Optional has three advantages:
  • The type system describes what is required or optional without the need for additional API documentation.
  • Static compile-time enforcement via the type system that optionality has been catered for in the code.
  • Optional values can be cascaded to perform successive optional operations, which is particularly useful in functional style programming.
As an aside, another way of thinking of the Optional type is that it is a specialised collection which can contain only zero or one elements, and supports similar operations to collections/streams, plus some of it's own specialised operations. A method which returns a collection expresses optionality ("There may be zero or more results"), whereas Optional is more specific ("There may be zero or one result").

When not to use Optional

Or "How to avoid the Law of the instrument". For input parameters this is fairly clear - don't use Optional when they are in fact always required. But for method return values it's less so. 

On the one hand, returning Optional allows the caller to decide how to handle the missing case - but on the other, it means the caller always has to handle the missing case even if it's something that cannot realistically be recovered from. This is analogous to methods which throw checked exceptions when the caller cannot be expected to recover - in both cases the caller is likely to have to throw an unchecked exception, which adds noise to the code if this has to be repeated many times. I think the answer here is subjective, but the metric I personally use is "Are there inputs which the caller can provide which are valid, but could still produce no result?" - if yes, then the return should be Optional, if no, then the method should treat the invalid inputs as a bug and throw a runtime exception.

Another case where the use of Optional has be to considered carefully is where is acts against the fail-fast principles - by cascading optional operations we can defer failing to a later point, but sometimes it would be better to fail immediately, particularly if this means more useful error diagnostic information can be provided. Often the deciding factor here is whether the process is interactive (in which case it may be sensible to ask the user to take some action) vs batch (where it often makes more sense to fail as early as possible with as much detail as possible).

I intend to go into more detail on these cases in a future article.

Elvis is not Optional

Some languages provide operators to make null handling simpler, such as the Elvis and related safe navigation operators in Groovy.

Although these operators also address issues caused by the presence of null, they are fundamentally different from Optional:
  • Safe navigation operators are syntactic sugar, useful when writing code in a style in which null is an expected value.
  • Optional, on the other hand, is an extension of the type system, and should be used when writing code in a style in which null is not considered to be an expected value.
In a language which allows null, both can be useful - I believe adding safe navigation operators to Java is still being considered. However it would probably be a mistake to mix the two styles in a single code base (other than for interfaces to third-party code).

So how will Optional save you from null?

In theory, it won't. Since nothing fundamental has changed in Java 8 with relation to nullability, it's possible for variable of type Optional to be set to null, or even more insidiously set to an instance of Optional containing null.

But in practice we can ignore this: When writing code using Optional, we should never set anything to null or test for null. If a bug results in null being referenced, a null pointer exception will be thrown at runtime as usual - but hopefully the chances of this are reduced because the use of Optional in the type system will have forced us to design the code to cater for all cases of optionality.

The main problem with this approach is dealing with legacy or library code which does confer meaning to null, but it is usually possible to either refactor the code, or provide a facade.

The null situation itself in Java may improve in future - although it's extremely unlikely null can ever be removed from Java entirely, efforts such as the Checker Framework mean that static analysis of Java code to identify potential issues is increasingly useful.

Conclusion

When used in code written in a style in which null is never intentionally used, Optional can help express "optionality" in an elegant manner that the compiler can understand and enforce. However since the concept of null is so deeply embedded in the Java language, it's not likely to go away anytime soon - in practice this may matter less than you think.

1 comment: