Java 8 Optional Code Smell

Optional<T> class encapsulates the optional value. Optional value means that either the value might or might not be present. It represents immutable container of non-null reference to T or nothing at all.

This article is part of ongoing series of Optional. Read about it more here.

In this article, we will see how not to use Optional. Optional is usually used as a replacement for null. This is a misconception. Optional must be used is to represent whether a value is present or not. For instance, what to do while finding maximum in array or collection if the container is empty or null? What are our options?

  1. Return null. Then client needs to handle the null.
  2. Throw Exception. Exceptions should be reserved for exceptional problem. If the container is empty or null, in my humble opinion, this does not qualify for an exceptional condition.

As of Java 8 we can now use a container i.e. Optional that indicates whether the value is present or absent.

Again remember, Optional is not replacement for null it is a container to indicate whether value is present or not.

Let’s work on below example:

List<Transaction> transactions = ...
List<Transaction> result = 
		Optional.ofNullable(transactions)  
				.map(Collection::stream)
				.orElse(Stream.empty())
				.filter(txn -> 
						txn.merchant()
							.name()
							.equals("some merchant name"))
				.collect(Collectors.toList());
  1. Optional.ofNullable(transactions) : Creates Optional object if transactions is not null else returns empty Optional instance.
  2. map(Collection::stream) : If the transactions is present i.e. not null then create a stream.
  3. orElse(Stream.empty()) : If the transactions is not present i.e. null then create an empty stream.
  4. filter(txn -> txn.merchant() .name() .equals(“some merchant name”)) : filter the transactions based on name
  5. collect(Collectors.toList()) : collect the filtered transactions into List.

Now this might sound that we are using Optional API in a right way but if you look closely you can encounter several flaws. Let’s go over them one by one.

Flaw No 1.

Optional.ofNullable(transactions)

This statement does not take into consideration on why is Optional really needed, let me tell you why. transactions is of type List<Transaction>. This particular List is returned from some method. For Collections it is preferable to return empty collection instead of returning null(ITEM 54: RETURN EMPTY COLLECTIONS OR ARRAYS, NOT NULLS, Effective Java Third Edition). Returning null forces a client to make a null check. This brings us to second problem with this statement i.e. using Optional as replacement for null checks.

if(transactions == null) {
 // some code
}

Above condition is better to make a null check. Why? Because Optional.ofNullable(..) will create Optional<T> object if the transactions object is not null. This beats the purpose of Optional as it is being used as replacement for null.

Flaw No 2.

map(Collection::stream)

This is interesting. map method accepts a Function. This function’s job is to convert List<Transaction> to Stream<Transaction> so we can iterate on List<Transaction> in declarative way and get job done. Now the interesting part. While Function returns Stream<Transaction> the map(Function) returns Optional<Stream<Transaction>>. Unnecessary complexity. Flaw No 3, describes how this unnecessary complexity bugs us.

Flaw No 3.

orElse(Stream.empty())

Now why use this statement at all? Remember when we used Optional.ofNullable(transactions), it can return empty Optional if the transactions is null. So now when we have to convert Optional<Stream<Transaction>> to Stream<Transaction> and if the empty optional is returned we need to make sure we handle that condition too. orElse method returns Stream.empty() if the Optional is empty else it will just return Stream<Transaction>.

There are several levels of unnecessary complexity involved by using Optional<> API in wrong way. Sometimes it might be incredibly easy to use Optional<> but it just might not be the right use case. One thing to keep in mind while using Optional<> is, Is value present or absent? Ask this question and you will know whether to use Optional<> or not.

Now let us assume that transactions returned is not null. It can have a List<Transaction> whose size can be >= 0 (Zero). Then how can we write that code?

List<Transaction> result = 
		transactions.stream()
					.filter(txn -> 
							txn.merchant()
								.name()
								.equals("some merchant name"))
					.collect(Collectors.toList());

In this article, we saw how not to use Optional as it will create unnecessary complexity. While returning any type of Collection or Map it is preferable and is best practice to return empty collection or empty map rather than null. Avoid null as much as possible.

Leave a Reply

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