An optional epiphany

Uzi Landsmann
Oct 29, 2020 · 4 min read

Java’s Optional class is great — it gives your methods a way to signal to their callers that they might not have a proper answer to give. And all that without returning null or throwing an exception. Using the Optional methods map(), orElse(), orElseGet() and orElseThrow() can be a great way to skip null checks and to make your code more fluent. However, there’s a nasty catch here, hiding in the bushes, which might bite you if you’re not careful enough. Read on to learn all about it if you dare.

It all started with a simple test. We’ve pre-populated a database with some data and this particular test was written to validate that the data (in this case, a report period) was fetched properly (method findByPeriodId() returns an Optional):

@Test
void shouldFetchPeriod() {
final var period = periodService.findByPeriodId(44)
.orElse(fail("Could not find period 44"));

assertThat(period.getPeriodId(), is(44));
// some more asserts
}

Simple, right? But for some reason, it failed. The error I got was this:

AssertionFailedError: Could not find period 44

This baffled me, because I could see that the pre-loaded data was actually there in other tests and that our frameworks (we use Testcontainers with a Mysql container and use Flyway to populate it) worked as they should.

So what could possibly go wrong? Here was my usual suspects list:

  1. The particular period (44) was not populated properly
  2. Period 44 was there, but someone wrote a test that deleted it
  3. PeriodService was not working as it should
  4. PeriodRepository (called by PeriodService) was not working as it should
  5. Intellij was playing a prank on me

Debugging the code revealed that all of these suspects had reliable alibis (including Intellij, after invalidating it’s cache and restarting it). So I decided that the culprit must be the Optional and focused my attention on it.

The following is an excerpt from the interrogation that followed:
me: why are you failing? I can see that you’re holding period 44. What are you playing at?
op: …
me: playing tough huh? what are you hiding?
orElse() should only be called if the optional value is null, which I can clearly see it is not.
op: …
me: show me your
orElse() implementation!
op: ok…

/**
* If a value is present, returns the value, otherwise returns
* {
@code other}.
*
*
@param other the value to be returned, if no value is present.
* May be {
@code null}.
*
@return the value, if present, otherwise {@code other}
*/
public T orElse(T other) {
return value != null ? value : other;
}

me: ok, so value, which in this case is the instance of Period , is not null, so the other value should not be returned. Hmmm…

And then it struck me. What is other, anyway? It’s an argument that is sent to a method. An evaluated argument. If you have a method that looks like this:

void printme(int x) {
System.out.println(x + " is an fine looking int");
}

…and you call it like this:

printme(2 + 3);

…then 2 + 3 will be evaluated before they are sent to the method. printme() will be called with argument 5. And this is exactly what happened in my test! The argument was evaluated before orElse() was called! and in my case it was… fail().

With that in mind, I understood two things:

  1. I’m stupid
  2. There should be a better way

Reiterating through these two facts, I realised that the solution was right there, in front of me. And it was called orElseGet(). It’s implementation looks like this:

/**
* If a value is present, returns the value, otherwise returns the result
* produced by the supplying function.
*
*
@param supplier the supplying function that produces a value to be returned
*
@return the value, if present, otherwise the result produced by the
* supplying function
*
@throws NullPointerException if no value is present and the supplying
* function is {
@code null}
*/
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}

Unlike orElse() , which takes an evaluated value as argument, orElseGet receives a Supplier, which only gets invoked when needed, which in my case should be never. As shown above, the value is not null, hence the supplier will not get invoked and fail() will not be called. The new test now looked like this:

@Test
void shouldFetchPeriod() {
final var period = periodService.findByPeriodId(44)
.orElseGet(() -> fail("Could not find period 44"));

assertThat(period.getPeriodId(), is(44));
// some more asserts
}

Amazingly, or perhaps not so amazingly, it didn’t fail anymore.

Perhaps you saw my mistake immediately and thought that this whole article was silly as it was obvious to you that fail() was called directly when the argument was evaluated. But for me this was no less than an epiphany. And I finally understood why both of these methods, orElse() and orElseGet() exist. I will never doubt an Optional again.

By the way, what happens if the Optional that is returned from a method is neither empty nor containing a value, but is simply null? Eh…. let’s just pretend I never said that.

Webstep

Webstep is an IT consultant company composed of 400…

Uzi Landsmann

Written by

Software developer at Webstep

Webstep

Webstep

Webstep is an IT consultant company composed of 400 employees located in Sweden and Norway. We have consultants who are experts within, System Development, Business Intelligence, IoT, Data Science (Machine Learning/AI) and IT-Management. We love to learn new things and share it w

Uzi Landsmann

Written by

Software developer at Webstep

Webstep

Webstep

Webstep is an IT consultant company composed of 400 employees located in Sweden and Norway. We have consultants who are experts within, System Development, Business Intelligence, IoT, Data Science (Machine Learning/AI) and IT-Management. We love to learn new things and share it w

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store