Unchecked Optional Access

ID

java.no_access_empty_optional

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Java

Tags

reliability

Description

Reports Optional.get() where nothing in reach ever checks the value is there.

A method counts as guarded, and nothing in it is reported, when its body calls isPresent() or isEmpty() anywhere — on any Optional — or when it makes an unqualified call to a method declared in the same compilation unit whose own body does. Developers routinely factor the check into a one-line sibling predicate, and the check is not forgotten there, just named:

if (shouldCleanProjectHomepage(projectOptional, branchOptional)) {  // not reported: the
  cleanUserHomepageInDb(dbSession, user);                           // predicate below checks
  return empty();                                                   // both Optionals
}
homepage.setComponent(projectOptional.get().getKey());

private boolean shouldCleanProjectHomepage(Optional<ProjectDto> p, Optional<BranchDto> b) {
  return !p.isPresent() || !b.isPresent() || !userSession.hasEntityPermission(USER, p.get());
}

Rationale

get() throws a bare NoSuchElementException when the Optional is empty, naming neither the value that was missing nor why it was expected. Calling it without the method ever checking presence is usually a forgotten guard rather than a deliberate choice:

Optional<User> user = repository.findById(id);
return user.get().getName(); // throws NoSuchElementException if the id does not exist

Exceptions

orElseThrow() is not reported, although it also throws on an empty Optional. It is the idiom for "an absent value is a contract violation here", which is the opposite of a forgotten check — and in practice it usually carries a supplier naming a domain exception, so the failure explains itself:

User user = repository.findById(id).orElseThrow(() -> new UserNotFoundException(id)); // not reported

Remediation

Check presence first, or use one of the accessors that does not require it:

Optional<User> user = repository.findById(id);
if (user.isPresent()) {
  return user.get().getName();
}
return "unknown";

What is not reported

Beyond a guarded method, a single access is accepted when the Optional is handed to a throwing validator first — a check/require/ensure/validate-prefixed call taking that same Optional as an argument, as in checkFoundWithOptional(group, "…​") before group.get() — or when the access sits lexically inside a map, flatMap, filter or ifPresent lambda taken on that same Optional, since such a lambda runs only when the value is present. orElseGet and or are pointedly not in that list: their lambda runs only when the value is absent, so a get() in there is the defect, not an exception to it.

The analysis is coarse on purpose. A guard is never tied to the access it covers — a method checking a different Optional, or checking on a path that does not reach the access, is still accepted. Only method bodies are scanned, not constructors or initializer blocks. A var-declared local whose type cannot be inferred is not recognized as Optional and is silently not reported.

Three guard shapes are still missed and produce known false positives, because closing them needs real flow analysis: a check on the collection rather than the Optional (if (m.size() == 1) { …​ m.keySet().stream().findFirst().get(); }), a non-Optional presence predicate on the same key (if (!entry.hasField(f)) return; then entry.getField(f).get()), and a precondition established in a different method.

© 2026 Xygeni. All rights reserved