Handle NullPointerException in Controller, Service and DAO Layer using Java 8 Optional Class

1. Overview

In this post, we will see how to use Java 8 Optional class effectively to handle null pointer exception in real time projects. How to handle null pointer in different layers like the controller layer, service layer, and DAO layer.

Key points about Java 8 Optional class:
  • null references have been historically introduced in programming languages to generally signal the absence of a value.
  • Java 8 introduces the class java.util.Optional to model the presence or absence of a value.
  • You can create Optional objects with the static factory methods Optional. empty, Optional.of, and Optional.ofNullable.
  • The Optional class supports many methods such as map, flatMap, and filter, which are conceptually similar to the methods of a stream.
  • Using Optional forces you to actively unwrap an optional to deal with the absence of a value; as a result, you protect your code against unintended null pointer exceptions.
  • Using Optional can help you design better APIs in which, just by reading the signature of a method, users can tell whether to expect an optional value. In this article, we will see how to use Java 8 Optional In Real-time Projects
Let's take an example of Creating CRUD Operations using Spring and Hibernate Framework.

In typical Spring MVC web application, there are three layers namely ControllerService, and DAO layer.

Let's see the usage of Optional Class API in each of layer.

2. Usage of Optional Class API in DAO Layer

In DAO layer, we write the code to retrieve a record by id or some fields from the database, basically, we are not sure whether the record exists or not, in that case, the method returns the null reference. Let's write a code to avoid such null pointer exception.
Example:
@Override
public Optional<User> getUserById(Integer userId) {
 return Optional.ofNullable((User) sessionFactory.getCurrentSession()
   .createCriteria(User.class).add(Restrictions.eq("userId", userId))
   .uniqueResult());
}

@Override
public Optional<User> loadUserByUsername(String userName) {
 Session session = sessionFactory.getCurrentSession();
 Criteria criteria = session.createCriteria(User.class)
   .add(Restrictions.eq("userName", userName))
   .add(Restrictions.eq("enabled", true));
 return Optional.ofNullable((User) criteria.uniqueResult());

}
Note that the usage Optional.ofNullable() static factory method which returns value if present otherwise returns an empty Optional instance.

3. Usage of Optional Class API's in Service Layer

Let's see how to use this code in Service Layer. In Service Layer, we need to handle whether the record exists or not. If it exists then return the object otherwise throw an exception or report an error to the client.
@Override
@Transactional
public User loadUserByUsername(String userName) {
return userDao.loadUserByUsername(userId).orElseThrow(
   () -> new ResourceNotFoundException(Integer.valueOf(userId)));
}

@Override
@Transactional
public void deleteUser(Integer userId) throws BaseException, ResourceNotFoundException {

 if (userId == null) {
  throw new BaseException(" Request param can't be null . " + userId);
 }
 final User user = userDao.getById(userId)
   .orElseThrow(() -> new UnauthorizedRequestException("User not exist"));
 user.setEnabled(false);
 userDao.update(user);

}


@Override
@Transactional(readOnly = true)
public User getUserById(Integer userId) throws BaseException {

 if (userId == null) {
  throw new BaseException(" Request param can't be null . " + userId);
 }

 return userDao.getById(userId).orElseThrow(
   () -> new ResourceNotFoundException(Integer.valueOf(userId)));
}
Note that the usage orElseThrow() method which returns value if present otherwise throws an exception. In the above, we have used the Optional.orElseThrow() method with a lambda expression.

4. Usage of Java 8 Optional API in Controller Layer

Let's assume, UserService interface in injected in UserController class then we can use UserService interface methods in UserController directly.

In this example, note that the usage isPresent() method of Optional Class used.
@RequestMapping(path = "/users/{userId}", 
 method = RequestMethod.GET, 
 produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody ResponseEntity<User> getUser(
  @PathVariable("userId") String userId) {
 Optional<User> user = userService.getUser(userId);
 if (user.isPresent()) {
  return ResponseEntity.ok().body(user.get());
 }
 return ResponseEntity.badRequest().build();
}
One more use case is, if you have used the Generic Dao layer then the Optional Class API can be used like :
@Override
public Optional<T> getById(final K id) {
 try {
  return Optional.ofNullable((T) getSession().get(getType(), id));
 } catch (final RuntimeException re) {
  LOGGER.error("get failed", re);
  throw re;
 }
} 

5. Complete Source Code Reference

Let's create a User POJO class.
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.codehaus.jackson.annotate.JsonManagedReference;

@Entity
@Table(name = "user")
public class User {

 private int userId;
 private String userName;
 private String password;
 private boolean enabled;
 private String firstName;
 private String lastName;
 private Long dateCreated;
 private Long dateUpdated;
 private long lastAccess;
 private long lastLogin;
 private String email;

 public User() {

 }

 public User(int userId, String userName, String password, boolean enabled
   ) {
  super();
  this.userId = userId;
  this.userName = userName;
  this.password = password;
  this.enabled = enabled;
 }

 
 
 public User(String userName, String password, boolean enabled,
   String firstName, String lastName, Long dateCreated, Long dateUpdated,
   long lastAccess, long lastLogin, String email) {
  super();
  this.userName = userName;
  this.password = password;
  this.enabled = enabled;
  this.firstName = firstName;
  this.lastName = lastName;
  this.dateCreated = System.currentTimeMillis();
  this.dateUpdated = System.currentTimeMillis();
  this.lastAccess = lastAccess;
  this.lastLogin = lastLogin;
  this.email = email;
 }

 @Id
 @Column(name = "userId")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 public int getUserId() {
  return userId;
 }

 public void setUserId(int userId) {
  this.userId = userId;
 }

 public String getUserName() {
  return userName;
 }

 public void setUserName(String userName) {
  this.userName = userName;
 }

 public String getPassword() {
  return password;
 }

 public void setPassword(String password) {
  this.password = password;
 }

 public boolean isEnabled() {
  return enabled;
 }

 public void setEnabled(boolean enabled) {
  this.enabled = enabled;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public long getLastAccess() {
  return lastAccess;
 }

 public void setLastAccess(long lastAccess) {
  this.lastAccess = lastAccess;
 }

 public long getLastLogin() {
  return lastLogin;
 }

 
 public void setLastLogin(long lastLogin) {
  this.lastLogin = lastLogin;
 }
 
 public Long getDateCreated() {
  return dateCreated;
 }

 public void setDateCreated(Long dateCreated) {
  this.dateCreated = dateCreated;
 }

 public Long getDateUpdated() {
  return dateUpdated;
 }

 public void setDateUpdated(Long dateUpdated) {
  this.dateUpdated = dateUpdated;
 }

 public String getEmail() {
  return email;
 }

 public void setEmail(String email) {
  this.email = email;
 }

}
Create UserService.java Interface and it's implementation class UserServiceImpl.java
public interface UserService {
 User saveOrUpdateUser(User user) throws BadRequestException;
 boolean checkUserExist(String userName);
 Optional<User> loadUserByUsername(String userName);
 void deleteUser(int userId) throws BaseException, ResourceNotFoundException;
 List<UserResponse> getUsers() throws BaseException;
 Optional<User> getUserById(Integer userId) throws BaseException;
}
@Service
public class UserService implements IUserService {

 @Inject
 private IUserDao userDao;

 @Inject
 private PasswordEncoder passwordEncoder;

 @Inject
 private RoleService roleService;

 @Override
 @Transactional(readOnly = false)
 public User saveOrUpdateUser(User user) throws BadRequestException {
  validateUser(user); 
  this.userDao.save(user);
  return user;
 }

 private void validateUser(UserRequest userRequest) throws BadRequestException {

  if (userRequest == null) {
   throw new BadRequestException(" User object can't be null.");
  }

  if (userRequest.getEmail() == null) {
   throw new BadRequestException(" User email can't be null.");
  }

  if (userRequest.getFirstName() == null) {
   throw new BadRequestException(" User First name can't be null.");
  }

  if (userRequest.getLastName() == null) {
   throw new BadRequestException(" User last name can't be null.");
  }

  if (userRequest.getPassword() == null) {
   throw new BadRequestException(" Password can't be null.");
  }
 }

 @Override
 @Transactional
 public boolean checkUserExist(String userName) {
  final long count = userDao.checkUserExist(userName);
  return count > 0;
 }

 @Override
 @Transactional
 public User loadUserByUsername(String userName) {
 return userDao.loadUserByUsername(userId).orElseThrow(
    () -> new ResourceNotFoundException(Integer.valueOf(userId)));
 }

 @Override
 @Transactional
 public void deleteUser(Integer userId) throws BaseException, ResourceNotFoundException {

  if (userId == null) {
   throw new BaseException(" Request param can't be null . " + userId);
  }
  final User user = userDao.getById(userId)
    .orElseThrow(() -> new UnauthorizedRequestException("User not exist"));
  user.setEnabled(false);
  userDao.update(user);

 }

 @Override
 @Transactional(readOnly = true)
 public List<UserResponse> getUsers() throws BaseException {

  final List<UserResponse> listOfUsers = userDao.getUsers();
  if (listOfUsers == null) {
   throw new BaseException("Users not exist in database");
  }
  return listOfUsers;
 }

 @Override
 @Transactional(readOnly = true)
 public User getUserById(Integer userId) throws BaseException {

  if (userId == null) {
   throw new BaseException(" Request param can't be null . " + userId);
  }

  return userDao.getById(userId).orElseThrow(
    () -> new ResourceNotFoundException(Integer.valueOf(userId)));
 }

}
Let's Create UserDao.java Interface and it's implementation UserDaoImpl.java
public interface UserDao {
 User saveOrUpdateUser(UserRequest userRequest) throws BadRequestException;
 boolean checkUserExist(String userName);
 Optional<User> loadUserByUsername(String userName);
 void deleteUser(int userId) throws BaseException, ResourceNotFoundException;
 List<UserResponse> getUsers() throws BaseException;
 Optional<User> getUserById(Integer userId) throws BaseException;
}
@Repository
public class UserDao implements UserDao{

 @Autowired
 private SessionFactory sessionFactory;
 
 @Override
 public void saveOrUpdateUser(User user) {
  Session session = sessionFactory.getCurrentSession();
  session.saveOrUpdate(user);
 
 }
 
 @Override
 public Optional<User> getUserById(Integer userId) {
  return Optional.ofNullable((User) sessionFactory.getCurrentSession()
    .createCriteria(User.class).add(Restrictions.eq("userId", userId))
    .uniqueResult());
 }
 
 @Override
 public Optional<User> loadUserByUsername(String userName) {
  Session session = sessionFactory.getCurrentSession();
  Criteria criteria = session.createCriteria(User.class)
    .add(Restrictions.eq("userName", userName))
    .add(Restrictions.eq("enabled", true));
  return Optional.ofNullable((User) criteria.uniqueResult());

 }

 @Override
 public long checkUserExist(String userName) {
  Session session = sessionFactory.getCurrentSession();
  Criteria criteria = session.createCriteria(User.class)
    .add(Restrictions.eq("userName", userName))
    .add(Restrictions.eq("enabled", true));
  criteria.setProjection(Projections.rowCount());
  return (Long) criteria.uniqueResult();
 }
 
 @Override
 public List<UserResponse> getUsers(){
  Session session = sessionFactory.getCurrentSession();
  final List<UserResponse> users = new ArrayList<>();
  
  // get users code here
  return users;
 }
 
 @Override
 public void deleteUser(User user){
  Session session = sessionFactory.getCurrentSession();
  session.delete(user);
 }

}

6. Conclusion

In this post, we have learned how to use Java 8 Optional class to handle NullPointerException in different layers like controller, service and DAO layer.
This post is very useful in day to day project work so you may bookmark this page for future reference.

Comments