Guide: Java 8 Optional Class

Bugra Celik
Segmentify Tech Blog
7 min readAug 11, 2022

NullPointerException is perhaps the most important problem Java programmers face when writing code. To avoid this error, most Java programmers perform a null check control.

If you don’t check whether the input is null when writing code, you will most likely see the NullPointerException error in the logs a few days later, even if you don’t get an error at first. Because we always expect a variable operating with outside parameters as input to contain a value, but this can be a null value in some cases. If this value is null and we try to work with the null value, Java throws a NullPointerException. If it is not handled, our program will stop running.

As Java programmers, when we want to perform an operation on a value from an external source, we need to check whether the return value is null or not. We have if-else code blocks such as “if(variable is null), return true/ if(variable is { }), return false” in our code base.

Instead of always using if else in our code base, the optional classes introduced with Java 8 are now possible with functional programming by writing more straightforward code. This way, we can reduce our dependency on null checks and if-else code blocks and write cleaner code.

Let’s talk a bit about the Optional class in Java. Thanks to the two static methods in the Optional class, you can create a value of any type as an Optional class. Using the Optional.empty(); method, you can create an optional empty object if the return value is null. This can be considered as a kind of optional reference with no value.

This method returns us an empty optional class. We will clarify this later while using the isPresent and ifPresent methods. Let’s first look at the method of creating an optional class.

public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

Thanks to the static and generic “of” method, you can create a value of any type as Optional. As in line 4, you can use the objects of a class you have written as optional.

Optional<String> optional = Optional.of("optinal");
Optional<Integer> integer = Optional.of(1);
OptionalDouble optionalDouble = OptionalDouble.of(1.2);
Optional<Example> example = Optional.of(new Example());

How about creating a null value with an optional class, then? The answer is no. If you run the following code block, you will get the NullPointerException error.

Optional.of(null);

Let’s say we want to create an optional class with values from a source using Optional.of. And a question pops up in our minds, “Should we check the null cases with if-else again?” In this case, a method saves us.

Optional.ofNullable(null);

If the return value is null, thanks to OfNullable, you won’t get a NullPointerException error. Thanks to this method, we will get Optional.empty(); without a NullPointerException error.

This way, we can get an object of the Optional class. So let’s talk about what we can do with this object (reference) we have. First, let’s talk about the isPresent() method and get() method with an example.

With the isPresent method of the Optional class, we can determine if a value is present in the Optional class, that is, either a value present or a value created with Optional.empty();. The isPresent() method returns us a boolean value. If this returns true, the value is present in the optional instance and we can retrieve it with the get() method.

If we want to retrieve the value of an optional class with isPresent() false using the get() method, we get NoSuchElementException error. Therefore, we need to call the get() method when isPresent() is true and get the desired value before handling the optional class.

Optional<String> optional = Optional.of("hello word");
if (optional.isPresent()) {
System.out.println(optional.get());
} else {
System.out.println("optional class is not present");
}

In the code block above, we see that the optional object isPresent is checked, and the value within is written to the console using the get method. Can we simplify this process?

Of course! We can write the code block above in a single line using the functional programming that came into our lives with Java 8. We can call the function we specify thanks to the ifPresent method, which is a non-static method of the Optional class.

optional.ifPresent(System.out::println);

With the help of Optional class and functional programming in Java 8, we can perform the operation that we would perform in 6 lines in just a single line. In this way, our code becomes more readable and less complicated. In the following example, the operation that we would perform in 6 lines is reduced to a single line thanks to the ifPresentOrElse method.

optional.ifPresentOrElse(System.out::println, () -> System.out.println("optional class is not present"));

Another method used in the Optional class is the orElse method. Thanks to this method, we can specify the desired value in “else” conditions. If the password is not generated in the following code block, “failed to create password” is written to the console.

class App {
public static void main(String[] args)
{
Random r = new Random();

for (int i = 0; i < 10; ++i) {
Optional<String> passwordOptional = RandomPasswordGenerator.getRandomPasswordIfSatisfied(r, r.nextBoolean());
System.out.printf("%s", passwordOptional.orElse("Şifre üretilemedi"));
}
}
}
class RandomPasswordGenerator {
public static Optional<String> getRandomPasswordIfSatisfied(Random r, boolean satisfied)
{
return satisfied ? Optional.of(StringUtil.getRandomTextTR(r, 10)) : Optional.empty();
}
}

Let’s look at another useful method, orElseThrow, with an example. It is a method that throws an exception if else, that is Optional.empty(). If the password cannot be created in the code block below, it will return NoSuchElementException.

class App {
public static void main(String[] args)
{
Random r = new Random();
for (int i = 0; i < 10; ++i) {
try {
Optional<String> passwordOptional = RandomPasswordGenerator.getRandomPasswordIfSatisfied(r, r.nextBoolean());
System.out.printf("%s", passwordOptional.orElseThrow());
}
catch (NoSuchElementException ignore) {
System.out.printf("Şifre üretilemedi");
}
}
}
}
class RandomPasswordGenerator {
public static Optional<String> getRandomPasswordIfSatisfied(Random r, boolean satisfied)
{
return satisfied ? Optional.of(StringUtil.getRandomTextTR(r, 10)) : Optional.empty();
}
}

Let’s explain another useful method, orElseGet, with an example. It invokes a callback to the function we specify as a parameter in Optional.empty();. In the following example, (() -> r.nextInt(100) + “”); a function callback will be invoked if else.

class App {
public static void main(String[] args)
{
Random r = new Random();
for (int i = 0; i < 10; ++i) {
Optional<String> passwordOptional = RandomPasswordGenerator.getRandomPasswordIfSatisfied(r, r.nextBoolean());
System.out.printf("%s", passwordOptional.orElseGet(() -> r.nextInt(100) + ""));
}
}
}
class RandomPasswordGenerator {
public static Optional<String> getRandomPasswordIfSatisfied(Random r, boolean satisfied)
{
return satisfied ? Optional.of(StringUtil.getRandomTextTR(r, 10)) : Optional.empty();
}
}

Another useful feature the optional classes provide us with is the “filter” method. We need to give a Predicate function as a callback for this filter method. Depending on whether the return value of this predicate method is true or false, we can get a new Optional object and write fluent code.

In the following example where a Predicate function is given in the filter method, If the return value is true, we have an object with Optional.of. If the return value is false, we have an object with Optional.empty. Then we can write fluent code using ifPresent or isPresent methods.

As seen in the example, filter(s -> s.length() > 7) is given as a predicate in the passwordOptional object. If the length of the return value is above 7, the value is written to the console with .ifPresent(System.out::println);. For lengths 7 or less than 7, no value gets written to the console.

class App {
public static void main(String[] args)
{
Random r = new Random();

for (int i = 0; i < 10; ++i) {
Optional<String> passwordOptional = RandomPasswordGenerator.getRandomPasswordIfSatisfied(r, 5, 10);
passwordOptional.filter(s -> s.length() > 7)
.ifPresent(System.out::println);
}
}
}
class RandomPasswordGenerator {
public static Optional<String> getRandomPasswordIfSatisfied(Random r, int min, int max)
{
return r.nextBoolean() ? Optional.of(StringUtil.getRandomTextTR(r, r.nextInt(max - min) + min)) : Optional.empty();
}
}

Finally, a word about the “Map” method, which is a useful method of the Optional class. The Map method in the Optional class is a non-static method that allows us to map from the class in question to another type of class.

Let’s think of it this way: You have a database where you store your users in the bank, and there are Persons in that database. You may not want to return all the information you have stored. For such cases, we have DTO (Data Transfer Object) classes. In these DTO classes, the data to be returned in response to the request is mapped into the DTO class and returned. In this way, we do not reveal the credit card number or the address data of the users.

In the following example, you can see the Person and PersonDto classes. In line 12, we get a new optional object by mapping an object of the Person class type to an object of the PersonDto class type. After checking the returned value with the personDto.isPresent() method of the personDto object, the ID, name, and surname of the personDto object get written to the console.

This process should not be considered as just writing to the console. We can return the information of this personDto object as a response to the services making requests to the Bank API. This way, we don’t have to share all the information about the user in question, but can return only the requested data as a response.

public class OptionalExample {
public static void main(String[] args) {
Person person = Person.builder().id(984)
.name("Bugra")
.surName("Celik")
.password("324jlJıQF")
.cardNumber(2385937102343443L)
.address("Istanbul")
.build();
Optional<Person> optionalPerson = Optional.of(person);
Optional<PersonDto> personDto = optionalPerson.map(val -> new PersonDto(val.getId(), val.getName(), val.getSurName()));
if (personDto.isPresent()) {
System.out.println(personDto.get());
}
}
}
@Getter
@Setter
@AllArgsConstructor
@Builder
class Person {
private Integer id;
private String name;
private String surName;
private String password;
private Long cardNumber;
private String address;
}
@Getter
@Setter
@AllArgsConstructor
@ToString
class PersonDto {
private Integer id;
private String name;
private String surName;
}

--

--