Geschreven door Ian van Nieuwkoop

Monads in Java: a short introduction

Development5 minuten leestijd

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?

The Problem
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.

Original code

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).

Alternative 1

Alternative 2

Alternative 3

 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:

  • A function to wrap any basic value, creating a new monad. Also called the return function.
  • And a function that allows you to perform operations on a wrapped data type (monad). Also called the bind function.

It must also adhere to the following three monad laws:

  • Left identity, passing an operation to a monad should yield the same result as applying the operation to the original value.
  • Right identity, passing a wrapper function to a monad should yield the same result as creating a new nomad.
  • Associativity, when chaining functions, it should not matter how the functions are nested.

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:

  • Does it represent a specific calculation? Yes, it encapsulated a mechanism for performing actions on values safely without causing a null pointer exception.
  • Is there a function for wrapping basic values and creating a new monad? Yes, ofNullable which takes any value and returns a new Optional instance containing that value.
  • Does it have a function to allow operations to be performed on its contained value? Yes, flatMap does the trick nicely.
  • Does it adhere to the three monad laws? The short answer is yes.

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.

 And now?
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.