Optional is a return type, not a field type
Optional was added to express one thing: this method may legitimately have no answer. It has since been used for four other things, and three of them are worse than the null they replaced.
Where it belongs
A method whose absence of result is a normal outcome, not an error.
Optional<Account> findByEmail(String email);
The caller cannot ignore the possibility. That is the entire value.
Where it does not
As a field. private Optional<String> middleName; costs an extra object per instance, is not serializable, and moves the null one level down rather than removing it — the field itself can be null. Use a nullable field and document it.
As a parameter. A method taking Optional<X> now has three input states: present, empty, and null. That is worse than two. Overload the method instead.
In collections. List<Optional<String>> is a list where absence is already expressible by not putting the element in.
The call that undoes the point
opt.get()
If you call get() without isPresent(), you have written a NullPointerException with extra steps and a longer name. The methods that make Optional worth having are the ones that keep you inside it:
findByEmail(email)
.map(Account::id)
.filter(id -> id > 0)
.orElseThrow(() -> new AccountNotFound(email));
The honest cost
Every Optional is an allocation. In a hot loop that matters and the profiler will say so. Outside a hot loop it does not, and the clarity is worth more than the object. Know which one you are in before optimising it away.
Applies to Java 8 and later.