A lookup for one region finds no data. Saying “the repository returns 404” confuses two responsibilities: the repository produces a data result indicating absence, while a service or HTTP layer decides whether absence means a failed request.
Assume the illustrative region Seocho has one metric and MissingRegion has none. Trace both through findByRegion() to see where Optional.empty() differs from an HTTP 404.
What does findByRegion return when no row exists?
Suppose RegionMetric is an entity containing one region's metrics, with at most one row per region:
interface RegionMetricRepository extends JpaRepository<RegionMetric, Long> {
Optional<RegionMetric> findByRegion(String region);
}findByRegion("Seocho") returns an Optional containing the entity. findByRegion("MissingRegion") returns Optional.empty(), not a 404 response. The Spring Data repository return-type reference also states that an Optional<T> query expects at most one result; more than one matching row is an error.
The diagram separates the repository result from the later HTTP decision. A zero-row result has no HTTP status by itself.
Why is Optional.empty not automatically HTTP 404?
Optional represents the presence or absence of a Java value. It does not represent an HTTP status. For a required single-region API, a service can choose to turn absence into a not-found exception:
@Service
class RegionMetricService {
private final RegionMetricRepository repository;
RegionMetricService(RegionMetricRepository repository) {
this.repository = repository;
}
RegionResponse getRegion(String region) {
return repository.findByRegion(region)
.map(RegionResponse::from)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "Region not found: " + region));
}
}For Seocho, map() creates a RegionResponse; the exception is not needed. For MissingRegion, mapping is skipped and orElseThrow() creates the exception. When an HTTP request invokes this service through a controller, that ResponseStatusException can produce a 404 response. A database connection failure is different from an empty result; converting every database error to 404 would conceal an outage.
Why not call get() directly?
repository.findByRegion(region).get() throws NoSuchElementException for an empty Optional, but that does not express the API's missing-resource policy. orElse(null) merely moves the absence to another place. For a required resource, keep the missing-case decision beside the lookup.
Distinguish zero rows from duplicates and database errors
| Situation | Repository result | Service/API decision |
|---|---|---|
One Seocho row | Present Optional | Convert to response |
Zero MissingRegion rows | Optional.empty() | Choose 404 for a required single-resource API |
| More than one row for a region | Result-size error | Investigate uniqueness and data integrity |
| Database connection or query failure | Data-access error | Diagnose the failure; do not relabel it 404 |
The Optional<RegionMetric> contract fits “zero or one.” Check that the data model enforces or otherwise maintains one row per region. Without that rule, duplicates should not be mistaken for absence.
Test the boundaries separately: the repository returns an empty Optional for zero rows; the service chooses an exception for that empty result; an HTTP integration test confirms the actual response status. This keeps the data result and API policy distinct.
Key takeaways
A zero-row findByRegion() result is Optional.empty(). A 404 is a later policy choice for a required resource. map() transforms a present value, while orElseThrow() handles absence. Duplicate rows and database failures are different problems and need separate diagnosis.

