/ FUNCTIONAL PROGRAMMING, API, OPTIONAL, STREAM

Optional.stream()

This week, I learned about a nifty "new" feature of Optional that I want to share in this post. It’s available since Java 9, so its novelty is relative.

Let’s start with the following sequence to compute the total price of an order:

public BigDecimal getOrderPrice(Long orderId) {
    List<OrderLine> lines = orderRepository.findByOrderId(orderId);
    BigDecimal price = BigDecimal.ZERO;       (1)
    for (OrderLine line : lines) {
        price = price.add(line.getPrice());   (2)
    }
    return price;
}
1 Provide an accumulator variable for the price
2 Add each line’s price to the total price

Nowadays, it’s probably more adequate to use streams instead of iterations. The following snippet is the equivalent to the previous one:

public BigDecimal getOrderPrice(Long orderId) {
    List<OrderLine> lines = orderRepository.findByOrderId(orderId);
    return lines.stream()
                .map(OrderLine::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
}

Let’s focus on the orderId variable: it may be null.

The imperative way to handle null values is to check it at the beginning of the method - and eventually throw:

public BigDecimal getOrderPrice(Long orderId) {
    if (orderId == null) {
        throw new IllegalArgumentException("Order ID cannot be null");
    }
    List<OrderLine> lines = orderRepository.findByOrderId(orderId);
    return lines.stream()
                .map(OrderLine::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
}

The functional way is to wrap the orderId in an Optional. This is what the code looks like using Optional:

public BigDecimal getOrderPrice(Long orderId) {
    return Optional.ofNullable(orderId)                            (1)
            .map(orderRepository::findByOrderId)                   (2)
            .flatMap(lines -> {                                    (3)
                BigDecimal sum = lines.stream()
                        .map(OrderLine::getPrice)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                return Optional.of(sum);                           (4)
            }).orElse(BigDecimal.ZERO);                            (5)
}
1 Wrap the orderId in an Optional
2 Find relevant order lines
3 Use flatMap() to get an Optional<BigDecimal>; map() would get an Optional<Optional<BigDecimal>>
4 We need to wrap the result into an Optional to conform to the method signature
5 If the Optional doesn’t contain a value, the sum is 0

Optional makes the code less readable! I believe that readability should trump code style every single time.

Fortunately, Optional offers a stream() method (since Java 9). It allows to simplify the functional pipeline:

public BigDecimal getOrderPrice(Long orderId) {
    return Optional.ofNullable(orderId)
            .stream()
            .map(orderRepository::findByOrderId)
            .flatMap(Collection::stream)
            .map(OrderLine::getPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
}

Here’s the summary of the type at each line:

Snippet Type

Optional.ofNullable(orderId)

Optional<Long>

stream()

Stream<Long>

map(orderRepository::findByOrderId)

Stream<List<OrderLine>>

flatMap(Collection::stream)

Stream<OrderLine>

map(OrderLine::getPrice)

Stream<BigDecimal>

reduce(BigDecimal.ZERO, BigDecimal::add)

BigDecimal

Functional code doesn’t necessarily mean readable code. With the last changes, I believe it’s both.

To go further:

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a trainer and triples as a book author.

Read More
Optional.stream()
Share this