Handling Null Values and Optional in Java Backend Development
This article discusses common null‑value pitfalls in Java services, compares returning null collections versus empty collections, introduces the Null‑Object pattern and JDK 8/Guava Optional, and provides practical guidelines using JSR‑303/JSR‑305 annotations to make APIs safer and more expressive.
Introduction
In many Java projects developers encounter scattered null‑value checks that make the code hard to understand and can lead to NullPointerExceptions. This article summarizes several techniques for handling nulls safely.
Null Values in Business Logic
Scenario
Consider a UserSearchService interface with two methods:
public interface UserSearchService {
List
listUser();
User get(Integer id);
}Problem Spot
When testing the interface with TDD we notice two questions:
Should listUser() return an empty list or null when there is no data?
Should get(Integer id) throw an exception or return null when the user does not exist?
Deep Dive into listUser
A typical implementation returns null for an empty result:
public List
listUser() {
List
userList = userListRepository.selectByExample(new UserExample());
if (CollectionUtils.isEmpty(userList)) {
return null; // spring util
}
return userList;
}Returning null forces callers to perform null checks and can cause NullPointerExceptions. A safer version returns an empty list:
public List
listUser() {
List
userList = userListRepository.selectByExample(new UserExample());
if (CollectionUtils.isEmpty(userList)) {
return Lists.newArrayList(); // guava
}
return userList;
}Now the method always returns a List , even when there are no elements.
Deep Dive into get
A naive implementation simply returns the result of a repository call, which may be null :
public User get(Integer id) {
return userRepository.selectByPrimaryKey(id); // may be null
}To make the contract explicit, add Javadoc with an @exception tag:
/**
* Get user information by ID.
* @param id user ID
* @return user entity
* @exception UserNotFoundException if the user does not exist
*/
User get(Integer id);Alternatively, express the possible absence with Optional :
public interface UserSearchService {
/**
* Get user information by ID.
* @param id user ID
* @return Optional containing the user or empty if not found
*/
Optional
getOptional(Integer id);
}Implementation:
public Optional
getOptional(Integer id) {
return Optional.ofNullable(userRepository.selectByPrimaryKey(id));
}Parameter Constraints
Whether the id parameter is mandatory can be enforced with annotations:
Strong constraint using JSR‑303: @NotNull Integer id
Documentation constraint using JSR‑305: @CheckForNull or @Nonnull
public interface UserSearchService {
/** Get user by ID */
User get(@NotNull Integer id);
/** Get user optionally */
Optional
getOptional(@NotNull Integer id);
}Null‑Object Pattern
When converting a domain object to a DTO, a null source object often leads to many field‑by‑field null checks. Define a special subclass that returns default values:
static class NullPerson extends Person {
@Override public String getAge() { return ""; }
@Override public String getName() { return ""; }
}Then the conversion becomes straightforward:
Person person = getPerson(); // may return NullPerson
personDTO.setDtoAge(person.getAge());
personDTO.setDtoName(person.getName());Using Optional can achieve the same without a concrete subclass:
Optional.ofNullable(getPerson()).ifPresent(p -> {
personDTO.setDtoAge(p.getAge());
personDTO.setDtoName(p.getName());
});Optional as Return Value
Returning Optional clearly signals that the user may be absent:
public interface UserService {
Optional
get(Integer id);
}For collections, prefer returning an empty list rather than Optional > to avoid double‑wrapping ambiguity.
Optional as Parameter – Not Recommended
Using Optional for method arguments creates unclear semantics (e.g., does an empty Optional mean “all users” or “no filter”?). Split the method into two overloads instead:
public interface UserService {
List
listUser(String username);
List
listUser(); // returns all users
}When a parameter can be null, document it with JSR‑303/JSR‑305 annotations instead of wrapping it in Optional .
Guidelines Summary
Return empty collections instead of null unless there is a compelling reason.
Use Optional for single‑value returns where absence is a normal case.
Never use Optional as a method parameter.
Avoid overusing Optional in bean getters; it pollutes code.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.