How to write "if optional is empty, call next method returning optional, if not return this non-empty optional" several times in a functional style?

Viewed 1188

Assume I have this method

public Optional<String> validateAndReturnErrorMessage(SomeEcommerceSale s) {
    Optional<String> errorWithItem = validateItem(s.getItem());
    if (errorWithItem.isPresent()) return errorWithItem;

    Optional<String> errorWithPayment = validatePayment(s.getPayment());
    if (errorWithPayment.isPresent()) return errorWithPayment;
    
    // assume I have several other cases like the 2 above

    return Optional.empty(); // meaning there were no errors

My problem is, since OrElse and OrElseGet return <T> and not Optional<T>, is there a native way to do rewrite this into a more functional way without having several loosely coupled return statements?

EDIT

I would like to check validations one by one, since they are external and heavy. This way, I would call only the necessary ones

3 Answers

This kind of situation is what Optional.or was added for. It was added in Java 9, so if you're still on Java 8 then you can't use it.

return validateItem(s.getItem())
    .or(() -> validatePayment(req.getPayment()))
    .or(() -> /*another one*/);

If you are stuck on Java 8, you could write a helper method

public static <T> Optional<T> firstNonEmpty(List<Supplier<Optional<T>>> supplierList) {
    for (Supplier<Optional<T>> supplier : supplierList) {
        Optional<T> value = supplier.get();
        if (value.isPresent()) return value;
    }
    return Optional.empty();
}

And it use it like so:

return firstNonEmpty(
    Arrays.asList(
        () -> validateItem(s.getItem()),
        () -> validatePayment(req.getPayment()),
        () -> /*another one*/
    )
);

Just use or:

If a value is present, returns an Optional describing the value, otherwise returns an Optional produced by the supplying function.

return validatePayment(s.getItem()).or(() -> validatePayment(s.getPayment()));

If you have a long/dynamic list of items to validate, you may want to use a stream:

return Stream.<Supplier<String>>of(s::getItem, req::getPayment, ...)
         .map(s -> this.validateItem(s.get()))
         .filter(Predicate.not(Optional::isEmpty)) //or filter(o -> !o.isEmpty())
         .findFirst();
Related