Blog

Null Is Not An Option: Use Optional Or Fail

make absence explicit
Black null void swallowing a glowing line of code while red alarms flash

Null is billion-dollar footgun with tiny syntax. Modern Java gives better options, and still teams return null like it is 2006.

If absence is valid, model it. If absence is invalid, fail loudly. Do not leave trap on floor for next developer.

Old Style Null Soup

Customer customer = repository.findById(id);
if (customer != null) {
    Address address = customer.address();
    if (address != null) {
        send(address.email());
    }
}

Every line asks permission from void. Business rule is hidden under defensive noise.

Worse version skips one check and high-load system starts throwing NullPointerException only for rare imported accounts.

Use Optional For Possible Absence

Optional<Customer> findById(CustomerId id) {
    return jdbc.query(...).stream().findFirst();
}

Customer customer = repository.findById(id)
    .orElseThrow(() -> new UnknownCustomerException(id));

Now caller must decide. This is good pressure.

Do not use Optional for fields in JPA entities. Do not use Optional parameter for every method. Use it mainly for return values where absence is normal.

Fail For Impossible Absence

Constructor should reject invalid state.

record TransferCommand(AccountId from, AccountId to, Money amount) {
    TransferCommand {
        Objects.requireNonNull(from, "from");
        Objects.requireNonNull(to, "to");
        Objects.requireNonNull(amount, "amount");
        if (amount.isZeroOrNegative()) {
            throw new IllegalArgumentException("amount must be positive");
        }
    }
}

Now service does not need guess if command is valid.

Pattern Matching Helps Readability

String describe(Object value) {
    if (value instanceof AccountId id) {
        return "account " + id.value();
    }
    if (value instanceof Money money) {
        return "amount " + money;
    }
    return "unknown";
}

Modern Java makes type checks clearer. It does not make null safe automatically. You still design absence.

Do Not Abuse Optional

Bad:

Optional<Optional<Customer>> customer;

Also bad: optional.get() because you were too lazy to model path.

Use map, flatMap, orElseThrow, and clear domain exceptions.

Final Rule

Null should be rare and contained at boundaries: database driver, JSON parser, old API.

Inside domain, absence is explicit or impossible. Anything else is delayed incident.