How to use Optional class (Java) - best practices
Tags: programming, java, cleancode, softwaredevelopment
Published: 2024-01-27 00:09:33

## Qick info about Optional 

Optional isn't meant to be stored as state.

Optional is not meant to be value. It is container for value so that developer can make checking on its value.

Optional is limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.
For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. 
You should almost never use it as a field of something or a method parameter. 
Also routinely using it as a return value for getters would definitely be over-use.

Following quote from an Oracle article:

> _It is important to note that the intention of the Optional class is not to replace every single null reference. Instead, its purpose is to help design more-comprehensible APIs so that by just reading the signature of a method, you can tell whether you can expect an optional value. This forces you to actively unwrap an Optional to deal with the absence of a value._


## How to surrive from null pointer exceptions

You don't want to get null value or NullPointerException when using Optional

**1. Don't assign null to an optional variable**
```java
Optional<Person> person = null;
```

Solution: use `empty()`
```java
Optional<Person> person = Optional.empty();
```

**2. Never call `get()` directly to get the value**
```java
Optional<Person> person = PersonService.getPerson();
Person myPerson = persion.get();
```

Solution: check value with `isPresent()` before calling `get()` 
```java
Optional<Person> person = PersonService.getPerson();
if (person.isPresent()) {
   Person myPerson = persion.get();
}
Person myPerson = persion.get();
```

## What to return when there is no value present

**1. Don't use isPresent()-get() to return default value**

```java
if(status.isPresent()) {
   return status.get();
} else {
   return "UNKNOWN";
}
```
Solution: use `orElse()`
```java
return status.orElse("UNKNOWN");
```

**2. Don't use `orElse()` to return computed value**
```java
// it is called even if "status" is not empty
return status.orElse(computeStatus());
```
Solution: use `orElseGet()`
```java
// computeStatus() is called only if "status" is empty
return status.orElseGet(this::computeStatus);
```

**3. Use `orElseThrow()` to throw a exception**
```java
return status.orElseThrow(Exception::new);
```

## How to consume optional values

**1. Use `ifPresent()` to consume value or `isPresentOrElse()` to handle empty case**
```java
status.ifPresent(System.out::println);
status.isPresentOrElse(
    System.out::println,
    () -> System.out.println("Status not found")
);
```

**2. Use `or()` to return other Optional**
```java
return status.or(() -> Optional.of("PENDING"));
```

**3. `orElse()`/`orElseThrow()` goes nicely with streams and lamdbas (don't break a chain)**

```java
return products.stream()
    .filter(e -> e.getPrice() > 200)
    .findFirst()
    .map(Product:getName)
    .orElse("Not found");
```

```java
Optional<Cart> cart = ...;
Product product = ...;
return cart.
    .filter(e -> e.getItems().contains(product))
    .orElseThrow();
```

## Anti-patterns

**1. Don't overuse Optional by chaining its methods for purpose of getting value**


```java
Status status = ...;
return Optional.ofNullable(status).orElse("Pending");
```
Solution
```java
Status status = ...;
return status != null ? status : "Pending";
```


**2. Don't use Optional to return empty Collections or Arrays**

Solution: Rely on `Collections.emptyList()`
```java
return items == null ? Collections.emptyList() : items;
```

**3. Don't use Optional as element in Collections or Maps**

**4. Avoid boxing and unboxing use non-generic Optional**
```java
OprionalInt price == OprionalInt.of(50); 
OprionalLong price == OprionalLong.of(50L); 

```

**5. When designing APIs don't declare filed of type Optional**

- it is not value itself

- doesn't implement Serializable. It is not intended for use as a property of Java Bean

**6. Do not use Optional as constructor or method argument.**  

Solution: make get method return Optional
```java
class Person {
    private final String name;
    
    Person(String name) {
        this.name = name;
    }

    public Optional<String> getName() {
       return Optional.ofNullable(name);   
    }
}
```

## Best practices

**1. There is no need to Unwrap Optionals for asserting or testing equality**
```java
Optional<String> actual = ...;
Optional<String> expected = ...;
assertEquals(expected, actual);
```

**2. Reject wrapped values using .filter()**
```java
return password
    .filter(p -> p.length() > 5)
    .isPresent();          
```

**3.Use `map()` or `flatMap()` to transform value - no need to use `isPresent()`**
```java
// transform name to uper case if null return Optional.empty() 
return lowerName
    .map(String::toUpperCase);          
```

**3.Use `stream()` to treat the Optional instance as Stream**
```java
List<Prodcut> products = productsId.stream()
   .map(this::fetchProductById)
   .flatMap(Optional::stream)
   .collect(toList());

Optional<Product> fetchProductById(String id) {
// implementation
}
```