Do you use the Optional class as it should be?

Mohamed Taman, Chief Solutions Architect

CEO @SiriusXI | Java Champion | Oracle ACE Alumni| Jakarta EE Ambassador| Author | Speaker.

>

_

25+ recipes to use Optional class effectively which is not optional.

@_tamanm

Publications

You can catch me (🤝)

Here 👇

Or Google 🕵 me

"Mohamed Taman"

  1. How to survive from null pointer exceptions.
  2. What to return/set when there is no value present?
  3. How to consume optional values effectively?
  4. How to avoid Optional anti-patterns?
  5. How to use Optional professionally?

Agenda

Oh, I am getting null even when I use Optional? 😤

QUESTION #1

CODE RECIPE #1

public Optional<Employee> getEmployee() {
    
    Optional<Employee> employee = null;
   
    ...
}

Never assign Null to an optional variable.

//Avoid

CODE RECIPE #1.1

public Optional<Employee> getEmployee() {
    
    Optional<Employee> employee = Optional.empty();
   
    ...
}

Since Java 8

Initialize an empty() optional variable and not to use null.

//Prefer
 // this is prone to be empty
 Optional<Employee> employee = HRService.getEmployee();
    
 /* 
   if "Employee" is empty then this code 
   will throw a java.util.NoSuchElementException
 */
 
 Employee myEmployee = employee.get();

CODE RECIPE #2

Never call Optional.get() directly to get the value.

//Avoid

Since Java 8

if (employee.isPresent()) {

    Employee myEmployee = employee.get();
    
    ... // do something with "myEmployee"
    
} else {

    ... // do something that doesn't call employee.get()
}

Since Java 8

CODE RECIPE #2.1

//Prefer

Check value with Optional.isPresent() before calling Optional.get().

public void callDynamicMethod(MyClass clazz, String methodName) throws ... {
    
    // contains an instance of MyClass or empty if "myMethod" is static
    Optional<myclass> myClass = clazz.getInstance();
    
    Method = MyClass.class.getDeclaredMethod(methodName, String.class);
    
    if (myClass.isPresent()) {
        method.invoke(myClass.get(), "Test");
    } else {
        method.invoke(null, "Test");
    }
}

CODE RECIPE #3

Don't use null directly when you have an Optional & need a null reference.

//Avoid
Method myMethod = ... ;
...

// contains an instance of MyClass or empty if "myMethod" is static
Optional<MyClass> myClassInstance = ... ;

...

myMethod.invoke(myClassInstance.orElse(null), ...);

Since Java 8

Use Optional.orElse(null) to return null.

//Prefer

CODE RECIPE #3.1

What to do when no value is present? 😕

QUESTION #2

public static final String USER_STATUS = "UNKNOWN";
...
public String getUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    
    if (status.isPresent()) {
        return status.get();
    } else {
        return USER_STATUS;
    }
}

CODE RECIPE #4

Don't use isPresent()-get() pair for setting/returning a value.

//Avoid
public static final String USER_STATUS = "UNKNOWN";


public String getUserStatus(long id) {
    
    Optional<String> status = ... ; // prone to return an empty Optional
    
    return status.orElse(USER_STATUS);
}

Use Optional.orElse() as an elegant alternative to set/return an already-constructed default object.

//Prefer

CODE RECIPE #4.1

public String computeStatus() {
    ... // some code used to compute status
}

public String getUserStatus(long id) {
    
    Optional<String> status = ... ; // prone to return an empty Optional
    
    // computeStatus() is called even if "status" is not empty
    return status.orElse(computeStatus()); 
}

CODE RECIPE #5

Don't use orElse() for setting/returning a computed value.

//Avoid
public String computeStatus() {
    ... // some code used to compute status
}

public String getUserStatus(long id) {
    
    Optional<String> status = ... ; // prone to return an empty Optional
    
    if (status.isPresent()) {
        return status.get();
    } else {
        return computeStatus();
    }
}

Ah, don't use isPresent() - get() it is not elegant.

//Avoid

CODE RECIPE #5.1

public String computeStatus() {
    ... // some code used to compute status
}

public String getUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    
    // computeStatus() is called only if "status" is empty
    return status.orElseGet(this::computeStatus);
}

Performance matters, use Optional.orElseGet() for setting/returning a computed value.

//Prefer

CODE RECIPE #5.2

Since Java 8

public String getUserStatus(long id) {
    Optional<String> status = ... ; // prone to return an empty Optional
    
    if (status.isPresent()) {
        return status.get();
    } else {
        throw new NoSuchElementException();        
    }
}

CODE RECIPE #6

Don't throw java.util.NoSuchElementException when there is no value using isPresent() - get().

//Avoid
public String getUserStatus(long id) {
    
    Optional<String> status = ... ; // prone to return an empty Optional
    
    return status.orElseThrow();
}

Throw a java.util.NoSuchElementException 

exception via an elegant alternative orElseThrow().

//Prefer

Since Java 10

CODE RECIPE #6.1

public String getUserStatus(long id) {
    
    Optional<String> status = ... ; // prone to return an empty Optional
    
    if (status.isPresent()) {
    
        return status.get();
        
    } else {
    
        throw new IllegalStateException(); 
    }
}

CODE RECIPE #7

Again, don't throw explicit exception when there is no value using isPresent() - get().

//Avoid
public String getUserStatus(long id) {
    
    Optional<String> status = ... ; // prone to return an empty Optional
    
    return status.orElseThrow(IllegalStateException::new);
}

Throw an explicit exception via

orElseThrow(Supplier<? extends X> exceptionSupplier).

//Prefer

CODE RECIPE #7.1

Since Java 8

How to consume Optional effectively?👌

QUESTION #3

Optional<String> status = ... ;

if (status.isPresent()) {
    
    System.out.println("Status: " + status.get());
}

CODE RECIPE #8

Don't use isPresent() - get() to do nothing if value is not present.

//Avoid
Optional<String> status = ... ;

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

Use ifPresent() to consume Optional value if present or do nothing if not.

//Prefer

Since Java 8

CODE RECIPE #8.1

Optional<String> status = ... ;

if(status.isPresent()) {

    System.out.println("Status: " + status.get());
    
} else {

    System.out.println("Status not found");
}

CODE RECIPE #9

Don't use isPresent() - get() to execute an Empty-Based action if value is not present.

//Avoid
Optional<String> status = ... ;

status.ifPresentOrElse(
    System.out::println, 
    () -> System.out.println("Status not found")
);

Since Java 9

CODE RECIPE #9.1

Use ifPresentElse() to consume Optional value if present or execute an empty based action if not.

//Prefer
public Optional<String> fetchStatus() {
    
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    
    if (status.isPresent()) {
        return status;
    } else {
        return defaultStatus;
    }  
}

CODE RECIPE #10

Don't use isPresent() - get() to set/return the other Optional when no value is present.

//Avoid
public Optional<String> getStatus() {
    
    Optional<String> status = ... ;
    
    return status.orElseGet(() -> Optional.<String>of("PENDING"));
}

Also, don't use orElse() - orElseGet() to do this job.

//Avoid

CODE RECIPE #10.1

public Optional<String> getStatus() {
    
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    
    return status.or(() -> defaultStatus);
    
    // or, without defining "defaultStatus"
    return status.or(() -> Optional.of("PENDING"));
}

Since Java 9

CODE RECIPE #10.2

Use Optional.or() to set/return other Optional.

//Prefer
List<Product> products = ... ;

Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst();
    
if (product.isPresent()) {
    return product.get().getName();
} else {
    return "NOT FOUND";
}

CODE RECIPE #11

Don't use isPresent() - get() with Lambdas.

//Avoid
Example 1
List<Product> products = ... ;

Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst();

return product.map(Product::getName)
    .orElse("NOT FOUND")

Also, don't do this, it is breaking the chain.

//Avoid

CODE RECIPE #11.1

List<Product> products = ... ;

return products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst()
    .map(Product::getName)
    .orElse("NOT FOUND");

CODE RECIPE #11.3

Optional.orElse()/orElseXXX() are perfect with Lambdas.

//Prefer
Optional<Cart> cart = ... ;
Product product = ... ;


if(!cart.isPresent() || 
   !cart.get().getItems().contains(product)) {
    throw new NoSuchElementException();
}

Don't check for value to throw an exception.

//Avoid

CODE RECIPE #11.4

Example 2
Optional<Cart> cart = ... ;
Product product = ... ;
...

cart.filter(c -> c.getItems().contains(product)).orElseThrow();

CODE RECIPE #11.5

Use .orElseThrow() with Lambdas.

//Prefer
public String getStatus() {
    
    String status = ... ;
    
    return Optional.ofNullable(status).orElse("PENDING");
}

CODE RECIPE #12

Don't overuse Optional by chaining its methods for the single purpose of getting value.

//Avoid
public String getStatus() {
    
    String status = ... ;
    
    return status == null ? "PENDING" : status;
}

CODE RECIPE #12.1

Avoid this practice and rely on simple and straightforward code.

//Prefer

Mmmm, how can I use Optional while designing my APIs?

🙇🏻‍♂️

QUESTION #4

public class Employee {
    
    private Optional<String> zip;
    private Optional<String> zip = Optional.empty();
    
}

CODE RECIPE #13

Do NOT declare any field of type Optional.

//Avoid
public class Employee {
    
    private String zip;
    private String zip = "";
    
}

CODE RECIPE #13.1

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

//Prefer
public class Employee {
    
    private final String name;               // can not be null
    private final Optional<String> postcode; // optional field, thus may be null
    
    public Employee(String name, Optional<String> postcode) {
        this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
        this.postcode = postcode;
    }
    
    public Optional<String> getPostcode() {
        return postcode;
    }
}

CODE RECIPE #14

Do NOT use Optional in constructors arguments.

//Avoid
public class Employee {
    private final String name;     // cannot be null
    private final String postcode; // optional field, thus may be null
    
    public Employee(String name, String postcode) {
        this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
        this.postcode = postcode;
    }
    
    public Optional<String> getPostcode() {
        return Optional.ofNullable(postcode);
    }
}

CODE RECIPE #14.1

Optional is not intended for use in constructors' arguments.

//Prefer
@Entity
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Column(name="employee_zip")
    private Optional<String> postcode; // optional field, thus may be null
    
    public Optional<String> getPostcode() {
       return postcode;
    }
     
    public void setPostcode(Optional<String> postcode) {
       this.postcode = postcode;
    }
}

CODE RECIPE #15

Using Optional in setters is another anti-pattern.

//Avoid
@Entity
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Column(name="employee_zip")
    private String postcode; // optional field, thus may be null
    
    public Optional<String> getPostcode() {
      return Optional.ofNullable(postcode);
    }
    
    public void setPostcode(String postcode) {
       this.postcode = postcode;
    }
}

CODE RECIPE #15.1

Do NOT use Optional in setters arguments.

//Prefer
public void renderCustomer(Cart cart, Optional<Renderer> renderer,
                           Optional<String> name) {     
    if (cart == null) {
        throw new IllegalArgumentException("Cart cannot be null");
    }
    
    Renderer customerRenderer = renderer.orElseThrow(
        () -> new IllegalArgumentException("Renderer cannot be null")
    );    
    
    String customerName = name.orElseGet(() -> "anonymous"); 
}

// call the method - don't do this
renderCustomer(cart, Optional.<Renderer>of(CoolRenderer::new), Optional.empty());

RECIPE CODE #16

Using Optional in methods arguments is another common mistake.

//Avoid
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    if (cart == null) {
        throw new IllegalArgumentException("Cart cannot be null");
    }
    if (renderer == null) {
        throw new IllegalArgumentException("Renderer cannot be null");
    }
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);

CODE RECIPE #16.1

Do NOT use Optional in methods arguments.

//Prefer
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    
    Objects.requireNonNull(cart, "Cart cannot be null");        
    
    Objects.requireNonNull(renderer, "Renderer cannot be null");        
    
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}

// call this method
renderCustomer(cart, new CoolRenderer(), null);

CODE RECIPE #16.2

Also, prefer to rely on NullPointerException.

//Prefer
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    
    MyObjects.requireNotNullOrElseThrow(cart, 
                () -> new IllegalArgumentException("Cart cannot be null"));
    
    MyObjects.requireNotNullOrElseThrow(renderer, 
                () -> new IllegalArgumentException("Renderer cannot be null"));    
    
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}

// call this method
renderCustomer(cart, new CoolRenderer(), null);

CODE RECIPE #16.3

Also, prefer to avoid NullPointerException and use IllegalArgumentException or other exceptions.

//Prefer
// write your own helper
public final class MyObjects {
    
    private MyObjects() {
        throw new AssertionError("Cannot create instances for you!");
    }
    
    public static <T, X extends Throwable> T requireNotNullOrElseThrow(T obj, 
        Supplier<? extends X> exceptionSupplier) throws X {       
        if (obj != null) {
            return obj;
        } else { 
            throw exceptionSupplier.get();
        }
    }
}
public Optional<List<String>> getCartItems(long id) {
    
    Cart cart = ... ;    
    
    List<String> items = cart.getItems(); // this may return null
    
    return Optional.ofNullable(items);
}

RECIPE CODE #17

Do Not Use Optional to return empty Collections or Arrays.

//Avoid
public List<String> getCartItems(long id) {
    
    Cart cart = ... ;    
    
    List<String> items = cart.getItems(); // this may return null
    
    return items == null ? Collections.emptyList() : items;
}

CODE RECIPE #17.1

Rely on Collections.emptyList(), emptyMap(), and emptySet().

//Prefer
Map<String, Optional<String>> items = new HashMap<>();

items.put("X1", Optional.ofNullable(...));
items.put("X2", Optional.ofNullable(...));

Optional<String> item = items.get("X1");

if (item == null) {    
    System.out.println("This key cannot be found");    
} else {
    String unwrappedItem = item.orElse("NOT FOUND");
    System.out.println("Key found, Item: " + unwrappedItem);
}

RECIPE CODE #18

Avoid Using Optional in Collections 

//Avoid
Map<String, String> items = new HashMap<>();

items.put("I1", "Shoes");
items.put("I2", null);
...
// get an item
String item = get(items, "I1");  // Shoes
String item = get(items, "I2");  // null
String item = get(items, "I3");  // NOT FOUND

private static String get(Map<String, String> map, String key) {
  return map.getOrDefault(key, "NOT FOUND");
}

CODE RECIPE #18.1

Not to introduce another layer into your Map, which is costly, also add performance hit.

//Prefer

CODE RECIPE #18.2

You can also rely on other approaches, such as

//Prefer
  • containsKey() method.
  • Trivial implementation by extending HashMap.
  • Java 8 computeIfAbsent() method.
  • Apache Commons DefaultedMap.
Map<Optional<String>, String> items = new HashMap<>();

Map<Optional<String>, Optional<String>> items = new HashMap<>();
//Avoid, this is even worse

RECIPE CODE #19

Since Java 8

public Optional<String> fetchItemName(long id) {
    
    String itemName = ... ; // this may result in null
 
    return Optional.ofNullable(itemName); // no risk for NPE    
}
//Prefer
public Optional<String> fetchItemName(long id) {
    
    String itemName = ... ; // this may result in null
    
    return Optional.of(itemName); // this throws NPE if "itemName" is null :(
}
//Avoid

Use Optional.ofNullable() instead of Optional.of()

// no risk to NPE
return Optional.of("PENDING");
//Prefr
// ofNullable doesn't add any value
return Optional.ofNullable("PENDING");
//Avoid

CODE RECIPE #19.1

Use Optional.of() instead of Optional.ofNullable().

Optional<Integer> price = Optional.of(50);

Optional<Long> price = Optional.of(50L);

Optional<Double> price = Optional.of(50.43d);

RECIPE CODE #20

Avoid Optional<T> and choose a Non-Generic Optional

//Avoid
// unwrap via getAsInt()
OptionalInt price = OptionalInt.of(50);

// unwrap via getAsLong()
OptionalLong price = OptionalLong.of(50L);

// unwrap via getAsDouble()
OptionalDouble price = OptionalDouble.of(50.43d);

CODE RECIPE #20.1

To avoid boxing and unboxing use non-generic Optional.

//Prefer

Since Java 8

I like Optional what I can do more? ⁨💪🤩

QUESTION #5

RECIPE CODE #21

Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");

assertEquals(expectedItem, actualItem);
//Prefer
Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");

assertEquals(expectedItem.get(), actualItem.get());
//Avoid

There is no need to Unwrap Optionals for asserting equality.

RECIPE CODE #22

Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);

// op1.equals(op2) => true,expected true
if (op1.equals(op2))
//Prefer
Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);

// op1 == op2 => false, expected true
if (op1 == op2)
//Avoid

Avoid Using Identity-Sensitive Operations on Optionals

public boolean isValidPasswordLength(User userId) {
    
    Optional<String> password = ...; // User password
    
    if (password.isPresent()) {
        return password.get().length() > 5;
    }
    return false;
}

RECIPE CODE #23

Avoid rejecting wrapped values based on .isPresent().

//Avoid
public boolean isValidPasswordLength(User userId) {
    
    Optional<String> password = ...; // User password
    
    return password.filter((p) -> p.length() > 5).isPresent();
}

CODE RECIPE #23.1

Reject wrapped values based on a predefined rule using .filter().

//Prefer

Since Java 8

//Java 8
public Optional<String> getCartItems(long id) {
    Cart cart = ... ; // this may be null
    ...    
    return Optional.ofNullable(cart);
}

public boolean isEmptyCart(long id) {
    Optional<String> cart = getCartItems(id);
    
    return !cart.isPresent();
}

RECIPE CODE #24

Return a boolean if the Optional is empty via .isPresent() if using Java 11.

//Avoid
public Optional<String> getCartItems(long id) {
    
    Cart cart = ... ; // this may be null
    
    return Optional.ofNullable(cart);
}

public boolean isEmptyCart(long id) {
    Optional<String> cart = getCartItems(id);
    
    return cart.isEmpty();
}

Since Java 11

//Prefer

Return a boolean if the Optional is empty via .isEmpty().

CODE RECIPE #24.1

Optional<String> lowername ...; // may be empty

// transform name to upper case
Optional<String> uppername;

if (lowername.isPresent()) {
    uppername = Optional.of(lowername.get().toUpperCase());
} else {
    uppername = Optional.empty();
}

RECIPE CODE #25

Don't use .isPresent() to check the values first then transform it.

//Avoid
//Prefer

Use .Map() and .flatMap() to transform values.

CODE RECIPE #25.1

Optional<String> lowername ...; // may be empty

// transform name to upper case
Optional<String> uppername = lowername.map(String::toUpperCase);
Example 1
// Avoid
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst();
    
String name;
if (product.isPresent()) {
    name = product.get().getName().toUpperCase();
} else {
    name = "NOT FOUND";
}

// getName() return a non-null String
public String getName() {
    return name;
}

CODE RECIPE #25.2

Example 2
//Prefer

Use .Map() to transform values.

List<Product> products = ... ;

String name = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst()
    .map(Product::getName)
    .map(String::toUpperCase)
    .orElse("NOT FOUND");
    
// getName() return a String
public String getName() {
    return name;
}

Since Java 8

// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst();
    
String name = null;
if (product.isPresent()) {
    name = product.get().getName().orElse("NOT FOUND").toUpperCase();
}

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

Since Java 8

//Prefer

Use .flatMap() to transform values.

CODE RECIPE #25.3

List<Product> products = ... ;

String name = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst()
    .flatMap(Product::getName)
    .map(String::toUpperCase)
    .orElse("NOT FOUND");
    
// getName() return an Optional
public Optional<String> getName() {
    return Optional.ofNullable(name);
}

RECIPE CODE #26

public List<Product> getProductList(List<String> productId) {
    
    return productId.stream()
        .map(this::fetchProductById)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(toList());
}

public Optional<Product> fetchProductById(String id) {
    return Optional.ofNullable(...);
}
//Avoid

Do we need to chain the Optional API with Stream API?

CODE RECIPE #26.1

Since Java 9

//Prefer

Use Optional.stream() to treat the Optional instance as a Stream.

* Also, we can convert Optional to List

public static <T> List<T> convertOptionalToList(Optional<T> optional) {
    return optional.stream().collect(toList());
}
public List<Product> getProductList(List<String> productId) {
    
    return productId.stream()
        .map(this::fetchProductById)
        .flatMap(Optional::stream)
        .collect(toList());
}

public Optional<Product> fetchProductById(String id) {
    return Optional.ofNullable(...);
}

Questions???

Do you use the Optional class as it should be?

By Mohamed Taman

Do you use the Optional class as it should be?

Do you use the Optional class as it should be? This presentation will show how to use Optional class effectively, with a list of best practices with around 25+ recipes to use Optional class Effectively which Is Not Optional.

  • 4,692