Be Functional With Java — get rid of if/else null checks with Vavr’s Option

Jaroslaw Kijanowski
SoftwareMill Tech Blog
4 min readNov 9, 2018

--

Photo credit: @anniespratt

Have you been there? You read about an awesome new tool or pattern and want to apply it in your code. You open your IDE and then… exactly… you’re not really sure how to use that new thing. You open the documentation, you read some tutorials and still have no clue.

The goal of this series is to provide you with a functional way to commonly used code snippets that will help you achieve the same result. In most cases, it should not only be more concise and readable, but also deliver all the goodies, functional programming promises.

In this post we’ll use Vavr’s Option to replace if/else statements that check if a given value is null or has a particular value.

Let’s start with the documentation:

Option is a monadic container type which represents an optional value. Instances of Option are either an instance of Some or the None.

You may say ‘mkey’ and look for a tutorial. Now you understand, Option is a wrapper for values, which could be null. Using it properly, you can avoid NullPointerExceptions as well as null checks.

Let’s discover ways to replace your current code in a functional style (all the examples are on GitHub).

The map() function will only be executed, when Option is Some() (nullableValue is not null). It will return the result of String::toLowerCase, which is a shorter way (method reference) to express this lambda: notNullValue -> notNullValue.toLowerCase(). Otherwise the getOrElse() method is executed returning its parameter.

Next:

Here forEach() is used to call exec.methodOne() passing nullableValue. But only, if nullableValue is not null.

Next:

This example requires to change the way you think about if/else. In the old-fashioned way, we check the value and based on that we execute the proper code. In the functional approach we first determine the proper value — either it’s Some() of nullableValue or Some of DEFAULT if nullableValue is null. That’s what orElse() in this case does — it returns a new Option container. We end up with an Option and can chain the next method forEach().

Next:

Instead of forEach() we have to use peek() here. It will execute the given function notNullForSure -> exec.methodTwo(notNullForSure), again shortened to exec::methodTwo. peek() will not terminate the chain, like forEach() does. This allows us to append another method to the chain — onEmpty() in this case, which executes a given function, when nullableValue is null.

Next:

This one is very similar to the previous example. To throw an exception on null use getOrElseThrow(). Again use peek() to not terminate the chain.

Next:

Having a value wrapped into an Option, we can perform additional checks. The filter method will let values pass, if they satisfy the provided predicate. Then the map() function kicks in. Its main purpose is to return "1st condition" regardless of the input value which is nullableValue. Therefore it was named ignore.
If nullableValue is null or does not pass the filter, getOrElse() is executed returning DEFAULT.

Next:

Nope, thank you, you may say. But let’s give it another try. In the functional approach you have all possible cases aligned and immediately see, which line is responsible for which case: None and None, None and Some, Some and None, Some and Some.
It’s not that readable with if/else. Moreover your IDE may highlight && nullableValueB != null from the first else, since it is redundant (always true) and suggests to remove it. Less is more, so you agree to remove it but when returning to this code after a while it will take you more time to figure out, how the whole if/else tree works.
One drawback of using Vavr’s pattern matching is its (powerful) syntax. The additional $() parameter for Some() allows you to provide built-in predicates as well as custom predicates as shown in the next example. In this case $()means any non-null value, that is wrapped by Some().

Then there is also the second parameter in Case: () -> run(() -> exec.methodOne(DEFAULT)).
Why not just exec.methodOne(DEFAULT)? Because the pattern matching mechanism would execute this method, regardless if the case is matched or not.
Ok, cool, then why not () -> exec.methodOne(DEFAULT)? That would work, if methodOne()would return something, like String, BigDecimal, Void. But it’s declared to be void.

Next:

As already mentioned, this example demonstrates the use of $(), where you can test if nullableValue satisfies a given condition, besides being a Some() which means being not null.
Since concat() called on nullableValue returns a String, we can simplify the function passed to Case and do not wrap it with run(() -> nullableValue.concat(...)).

Next:

Same example, just with functions declared as void. They have to be wrapped with run(() -> …) or declared as returning Void for example.

Next:

It’s your turn. Are you ready to open your IDE and ‘do it the functional way’?

To sum it up. The Option monad can replace your if/else statements checking if a value is null before continuing with your algorithm. You may ask, why Option and not jdk’s Optional — the answer is in this blog post.

Finally, an approach I’d advocate for is to wrap any nullable values into Option containers at the very beginning — where they are “born”. That could be an input param in the main() method of your standalone Java application or a query parameter being passed to a @RestController in your Spring Boot app.

You find all that easy peasy, are located in Poland, speak Polish and are looking for a job? Challenge us!

Looking for Scala and Java Experts?

Contact us!

We will make technology work for your business. See the projects we have successfully delivered.

--

--

Java consultant having experience with the Kafka ecosystem, Cassandra as well as GCP and AWS cloud providers. https://pl.linkedin.com/in/jaroslawkijanowski