In this blog I want to give a short introduction into the use of monads in Java. Interestingly, the concept of a monad has been around for quite some time now. Originating in mathematics it’s a design pattern which has previously only been popular in functional programming languages. However, it has found its way to Java. There is even a large chance that you have already been using them unknowingly. Especially, if you have been using the Optional and Stream libraries introduced in Java 8. So, what is a monad and how does it help me write clearer code?
First, I’m going to start with the problem that a monad helps us to solve. Its main goal is to help abstract away boiler plate code that isn’t the main concern of your code. Think of null checking, exception handling, validation, logging, concurrency, etc.
In this first code block, four classes have been declared. The first three classes represent a bank with customers which in turn may each have an active account. The Bank class has a single method which returns the Customer with the supplied id. The Customer class has a single method which returns the customer’s Account. The Account class also has a single method which returns the balance of the account. The final class ATM has a single method which takes a Bank instance and an id as a parameter and returns an account's balance.
The problem is that the methods in Bank, Customer and Account may return null. The id might not be correct, or the customer may no longer have an account at the bank, or the balance may not be available. This will result in the fetchBalance method of the ATM class throwing a NullPointerException, which will cause the program to crash.
We can fix this by refactoring the fetchBalance method to check if each value returned by Bank, Customer or Account happens to be a nullpointer (Alternative 1). We can all agree that this code works, but it’s not a pleasure to read. Alternatively, you could use nested if-statements to check for not null. However, this also has little effect on the readability of your code (Alternative 2).
Another alternative (Alternative 3) would be to place the code in a ‘try-catch’. This would improve the readability, but hide the cause of the exception, limiting your ability to later add some code to be able to recover from the exception.
What is a Monad?
So, what is a monad and how can it help us write more readable code? A monad is defined as any class (data type), which represents a specific calculation. It must implement at least these two functions:
It must also adhere to the following three monad laws:
The first two identity laws basically say that the only thing the return function is allowed to do is to wrap the object. It cannot manipulate the data in any way.
The final law ensures that it doesn't matter how bind functions are nested, the results are the same. Which simply means that the same sequence of operations will always produce the same value.
In Java the Optional class has all the necessary characteristics of a monad:
Other commonly used monads found in Java include: Stream<T> (and all the different types of streams) and CompletableFuture<T>.
How do monads solve our problem?
Now that we know what a monad is, let’s use the Optional monad, also known as the Maybe monad in other languages, to clear up our code.
Refactored code using the Optional monad.
We start by changing the return type of the getter methods in the Bank, Customer and Account classes, we do this by using the ofNullable method to wrap the return values. Thanks to the Optional we have also clearly declared that the methods may return null.
Then, we refactor the ATM's fetchBalance method. First, we wrap the Bank input parameter in the Optional monad. Finally, we use a chain of flatMap calls to perform operations on the wrapped value. Each time calling a getter method to replace the value inside the Optional with a new value. If at any time the value becomes null, any remaining transformations have no effect.
After refactoring, we end up with code that is easier to read and safer to run. The messy null pointer checks have been abstracted away and you are left with a chain of declarative transformations. Not bad.
I hope you enjoyed reading this introduction to monads in Java. Now that you have a general idea what a monad is, you should have an easier time following other more in-depth articles, blogs or presentations about this subject.