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???

Made with Slides.com