inspired by Java Champion Mohamed Taman
Original article: Do you use the Optional class as it should be?
=> 25+ recipes to use Optional class effectively which is not optional
Video => https://youtu.be/5kdBZsB563A
Agenda
- How to survive from null pointer exceptions (NPE)
- What to return/set when there is no value present?
- How to consume optional values effectively?
- How to avoid Optional anti-patterns?
- How to use Optional professionally?
❔❓ Question #1: oh, I am getting null even when I use Optional?
👉Code recipe #1
//Avoid
Never assign Null to an optional variable.
public Optional<Employee> getEmployee() { Optional<Employee> employee =null; ... }
//Prefer
Initialize an empty() optional variable and do not use null.
public Optional<Employee> getEmployee() { Optional<Employee> employee = Optional.empty(); ... }
👉Code recipe #2
//Avoid
Never call Optional.get() directly to get the value.
// 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();
//Prefer
Check the value with Optional.isPresent() before calling Optional.get().
if(employee.isPresent()){ Employee myEmployee = employee.get(); ... // do something with "myEmployee" } else { ... // do something that doesn't call employee.get() }
👉Code recipe #3
//Avoid
Don't use null directly when you have an Optional & need a null reference.
Method myMethod = ...; // contains an instance of MyClass or empty if "myMethod" is static Optional<MyClass> instanceMyClass = ... ; if( instannceMyClass.isPresent()){ myMethod.invoke(instanceMyClass.get(), ...); } else { myMethod.invoke(null, ...); }
//Prefer
Use Optional.orElse(null) to return null.
Method myMethod = ... ; // contains an instance of MyClass or empty if "myMethod" is static Optional<MyClass> instanceMyClass = ... ; ... myMethod.invoke(myClassInstance,orElse(null),...);
❔❓ Question #2: What to do when no value is present? ☹
👉 Code recipe #4
// Avoid
Don't use isPresent()-get() pair for setting/returning a value.
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; } }
// Prefer
Use Optional.orElse() as an elegant alternative to set/return an already-constructed default object.
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); }
👉 Code recipe #5
//Avoid
Don't use orElse() for setting/returning a computed value.
(Don't use neither isPresent/get).
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()); }
//Prefer
Performance matters, use Optional.orELseGet() for setting/returning a computed value.
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); }
👉 Code recipe #6
//Avoid
Don't throw java.util.NoSuchElementException when there is no value using isPresent()-get().
public String getUserStatus(long id) { Optional<String> status = ... ; // prone to return an empty Optional if (status.isPresent()) { return status.get(); } else { throw new NoSuchElementException(); } }
//Prefer
Throw a java.util.NoSuchElementException exception via an elegant alternative orElseThrow().
public String getUserStatus(long id) { Optional<String> status = ... ; // prone to return an empty optional return status.orElseThrow(); //Since Java 10 }
// or Prefer
Throw an explicit exception via orElseThrow(Supplier
exceptionSupplier).
public String getUserStatus(long id) { Optional<String> status = ... ; // prone to return an empty optional return status.orElseThrow(IllegalStateException::new); //Since Java 10 }
❔❓ Question #3: How to consume Optional effectively? 👌
👉 Code recipe #7
//Avoid
Don't use isPresent()-get() to do nothing if value is not present.
Optional<String> status = ... ; if(status.isPresent()){ System.out.println("Status: " + status.get()); }
//Prefer
Use ifPresent() to consume Optional value if present or do nothing if not.
Optional<String> status = ... ; status.ifPresent(System.out::println);
👉 Code recipe #8
//Avoid
Don't use isPresent()-get() to execute an Empty-Based action if value is not present.
Optional<String> status = ... ; if(status.isPresent()){ System.out.println("Status: " + status.get()); } else { System.out.println("Status not found"); }
//Prefer
Use ifPresentOrElse() to consume Optional value if present or execute an empty based action if not.
Optional<String> status = ... ;
status.ifPresentOrElse(
System.out::println,
()->System.out.println("Status: " + status.get()
);👉 Code recipe #9
//Avoid
Don't use isPresent()-get() to set/return the other Optional when no value is present.
public Optional<String> fetchStatus() { Optional<String> status = ...; Optional<String> defaultStatus = Optional.of("PENDING"); if(status.isPresent()){ return status; } else { return defaultStatus; } }
//Prefer
public Optional<String> fetchStatus() { Optional<String> status = ...; return status.orElseGet(()-> Optional.<String>of("PENDING"); }
// or Prefer
public Optional<String> fetchStatus() { Optional<String> status = ...; Optional<String> defaultStatus = Optional.of("PENDING"); return status.or(()->defaultStatus); }
👉 Code recipe #10
//Avoid
Don't use isPresent()-get() with Lambdas
Example 1
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"; }
// also avoid
Don't do this, it is breaking the chain.
List<Product> products = ...; Optional<Product> product = products.stream() .filter(p->p.getPrice < price) .findFirst(); return product.map(Product::getName) .orElse("NOT FOUND");
// Prefer
Optional.orElse()/orElseXXX() are perfect with Lambdas.
List<Product> products = ...;
Optional<Product> product = products.stream()
.filter(p->p.getPrice < price)
.findFirst();
.map(Product::getName)
.orElse("NOT FOUND");// also Avoid
Don't check for value to throw an exception.
Example 2
List<Product> products = ...;
Optional<Product> product = products.stream()
.filter(p->p.getPrice < price)
.findFirst();
.map(Product::getName)
.orElse("NOT FOUND");//Prefer
Use .orElseThrow with lambdas.
Optional<Cart> cart = ... ; Product product = ... ; cart.filter(c->c.getItems().contains(product)).orElseThrow();
👉 Code recipe 11
//Avoid
Don't overuse Optional by chaining its methods for the single purpose of getting value.
public String getStatus() { String status = ... ; return Optional.ofNullable(status).orElse("PENDING"); }
//Prefer
Avoid this practice and rely on simple and straightforward code.
public String getStatus() { String status = ...; return status == null ? "PENDING" : status; }
❔❓ Question #4: How can I use Optional while designing my APIs? 🙇♂️
👉 Code recipe #12
//Avoid
Do NOT declare any field of type Optional.
public Employee { private Optional<String> zip; private Optional<String> zip = Optional.empty(); }
Because it breaks serialization, it wastes memory heap space, and because Optional is to prevent null values or Entity fields can be null.
//Prefer
Optional doesn't implement Serializable. It is not intended for use as a property of a Java Bean.
public class Employee { private String zip; private String zip = ""; }
👉 Code recipe #13
//Avoid
Do NOT use Optional in contructors arguments.
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; } }
It's nonsense, because Optional means it might exist or not WHEN you RETURN it as a type, NOT when you pass it.
//Prefer
Optional is not intended for use in constructors' arguments.
public class Employee { private final String name; // can not 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); } }
If you wanna check the nullability, do it in a getter with Optional.ofNullable().
👉 Code recipe #14
//Avoid
Using Optional in setters is another anti-pattern.
@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; } }
Same rule than constructors parameters.
//Prefer
Do NOT use Optional in setters arguments.
@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; } }
Concentrate your focus on the getter.
👉 Code recipe #15
//Avoid
Using Optional in methods arguments is another common mistake.
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());
//Prefer
Do NOT use Optional in methods arguments.
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 the method renderCustomer(cart, new CoolRenderer(), null);
// also Prefer
Also, prefer to rely on NullPointerException.
public void renderCustomer(Cart cart, Renderer renderer, String name) { Objects.requireNonNull(cart, "Cart cannot be null"); Objects.requireNonNullE(renderer, "Renderer cannot be null"); String customername = Objects.requireNonNullElseGet(name, ()->"anonymous"); } // call the method renderCustomer(cart, new CoolRenderer(), null);
// also Prefer
Also, prefer to avoid NullPointerException and use IllegalArgumentException or other exceptions.
//write your own helper public final class MyObjects { private MyObjects() { throw new AssertionError("Cannot create instance 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(); } } }
How to use this? This is very easy:
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 the method renderCustomer(cart, new CoolRenderer(), null);
👉 Code recipe #16
//Avoid
Do not use Optional to return empty Collections or Arrays.
public Optional<List<String>> getCartItems(long id) { Cart cart = ... ; List<String> items = cart.getItems(); // this may return null return Optional.ofNullable(items); }
// Prefer
Rely on Collections.emptyList(), emptyMap(), emptySet().
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
//Avoid using Optional in Collections.
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); }
// Prefer
Do not introduce another layer into your Map, it is costly, and it strikes performance.
Map<String, String> items = new HashMap<>(); items.put("I1", "Shoes"); items.put("I2", null); ... // get an item String item = get(item, "I1"); // Shoes String item = get(item, "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") }
// Prefer
You can also rely on other approaches, such as
- containsKey
- () method
- Trivial implementation by extending HashMap
- Java 8 computeIfAbsent() method
- Apache Commons DefaultedMap
// Avoid, this is even worse
Map<Optional<String>, String> items = new HashMap<>(); Map<Optional<String>, Optional<String>> items = new HashMap<>();
This strikes performance.
👉 Code recipe #18
Use Optional.ofNullable() instead of Optional.of()
//Avoid
public Optional<String> fetchItemName(long id) { String itemName = ... ; // this may result in null return Optional.of(itemName); // this throws NPE if "itemName" is null ☹ }
//Prefer
public Optional<String> fetchItemName(long id) { String itemName = ... ; // this may result in null return Optional.ofNullable(itemName); // nor isk of NPE 🙂 }
Use Optional.of() instead of Optional.ofNullable() for constructed values (like constants).
//Avoid
// ofNullable doesn't add any value return Optional.ofNullable("PENDING");
//Prefer
//if no risk of NPE return Optional.of("PENDING");
👉 Code recipe #19
//Avoid
Avoid Optional
and choose a non-generci Optional.
Optional<Integer> price = Optional.of(50); Optional<Long> price = Optional.of(50L); Optional<Double> price = Optional.of(50.43d);
//Prefer
To avoid boxing and unboxing, use non-generic Optional.
// 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);
❔❓ Question #5: I like Optional, what can I do more?
👉 Code recipe #20
There is no need to unwrap Optionals for asserting equality.
//Avoid
Optional<String> actualItem = Optional.of("Shoes"); Optional<String> expectedItem = Optional.of("Shoes"); assertEquals(expectedItem.get(), actualItem.get());
//Prefer
Optional<String> actualItem = Optional.of("Shoes"); Optional<String> expectedItem = Optional.of("Shoes"); assertEquals(expectedItem, actualItem);
👉 Code recipe #21
Avoid using identity-sensitive operations on Optionals.
//Avoid
Product product = new Product(); Optional<Product> op1 = Optional.of(product); Optional<Product> op2 = Optional.of(product); // op1 == op2 =>false, expected true if(op1==op2)
//Prefer
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))
👉 Code recipe #22
//Avoid
Avoid rejecting wrapped values on .isPresent().
public boolean isValidPasswordLength(User userId){ Optional<String> password = ...; // User password if(password.isPresent()){ return password.get().length() > 5; } return false; }
//Prefer
Reject wrapped values based on a predefined rule using .filter().
public boolean isValidPasswordLength(User userId){ Optional<String> password = ...; // User password return password.filter((p)->p.length()>5).isPresent(); }
👉 Code recipe #23
//Avoid
Return a boolean if the Optional is empty via .isPresent() if using Java 11.
//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(); }
//Prefer
Return a boolean if the Optional is empty via .isEmpty().
//Java 11 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(); }
👉 Code recipe #24
//Avoid
Don't use isPresent() to check the values first then transform it.
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(); }
//Prefer
Use .Map() and .flatMap() to transform values.
Example 1
Optional<String> lowername ...; // may be empty // transform name to upper case Optional<String> uppername = lowername.map(String::toUpperCase);
//Prefer
Use .Map() to transform values.
Example 2
//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() returns a non-null String public String getName() { return name; }
//Prefer
List<Product> products = ... ; String name = products.stream() .filter(p->p.getPrice()>50) .findFirst() .map(Product::getName) .map(String::toUpperCase) .orElse("NOT FOUND"); // getName() returns a non-null String public String getName() { return name; }
//Prefer
Use .flatMap() to transform values.
//Avoid
List<Product> products = ... ; Optional<Product> product = products.stream() .filter(p->p.getPrice()<50) .findFirst(); String name; if(product.isPresent()){ name= product.get().getName().orElse("NOT FOUND").toUpperCase(); } else { name = "NOT FOUND"; } // getName() returns a non-null String public Optional<String> getName() { return Optional.ofNullable(name); }
//Prefer
List<Product> products = ... ; String name = products.stream() .filter(p->p.getPrice()<50) .findFirst() .flatMap(Product::getName) .map(String::toUpperCase) .orElse("NOT FOUND"); // getName() returns a non-null String public Optional<String> getName() { return Optional.ofNullable(name); }
👉 Code recipe 25
Do we need to chain the Optional API with STream API?
//Avoid
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(...); }
//Prefer
Use Optional.stream to treat the Optional instance as a Stream.
public List<Product> getProductList(List<String> productId){ return productId.stream() .map(this::fetchProductById) .flatMap(Optional::stream)// Since Java 9 .collect(toList()); } public Optional<Product> fetchProductById(String id){ return Optional.ofNullable(...); }
Also we can convert Optional to List
public static <T> List<T> convertOptionalToList(Optional<T> optional){ return optional.stream().collect(toList()); }