DEV Community

Syed Shujaat Hussain SHAH
Syed Shujaat Hussain SHAH

Posted on

Exception Handling and Validation in Spring boot

It is very important to handle the exceptions, logical errors and provide meaningful message to the client to avoid abnormal termination of the Restful APIs in Springboot.

Today we will learn how to implement Global Exception Handling as well as Class Level Exception Handling.

Pre-requisite : You must have knowledge about Springboot and Restful API Creation, Jpa (Java Persistance API) and a bit about Exception Handling in Java core.

We will be using java-17 to create spring boot template project with Spring Initilizr

We need the Following dependencies for the Project.
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bukhari</groupId>
    <artifactId>exceptionhandling-validation</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>exceptionhandling-validation</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Enter fullscreen mode Exit fullscreen mode

The application.properties File

#Dabase Configuration
spring.application.name=Exception-Handling-Application
server.port=8082
spring.datasource.url=jdbc:mysql://localhost:3306/libdb
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1

spring.jpa.hibernate.naming-strategy=org.hibernate.dialect.MySQL8Dialect
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.formate_sql=true
Enter fullscreen mode Exit fullscreen mode

Class Level Exception Handling

The Hierarchy of Code

The Hierarchy of java files in an IDE

Entity Book

package com.bukhari.exceptionhandlingvalidation.entity;


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String author;
    private Double price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

Enter fullscreen mode Exit fullscreen mode

The BookRepository

Make sure you import the Book from entity.Book package not from java.awt.print

package com.bukhari.exceptionhandlingvalidation.repository;

import com.bukhari.exceptionhandlingvalidation.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {

}
Enter fullscreen mode Exit fullscreen mode

The Exceptions that we want to handle are

  1. Book name cannot be empty.
  2. Book Author name cannot be empty.
  3. Price of Book must be $10 or greater than $10. (assuming price is always non-empty).

The BookService

package com.bukhari.exceptionhandlingvalidation.service;

import com.bukhari.exceptionhandlingvalidation.classexception.LibBusinessException;
import com.bukhari.exceptionhandlingvalidation.entity.Book;
import com.bukhari.exceptionhandlingvalidation.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookService {

    @Autowired
    private BookRepository bookRepository;

    public Book addBook(Book book) {

        //If the Name of the book is Empty
        if (book.getName().isEmpty()) {
            throw new LibBusinessException("801", "The Name of the Book can't be Empty");
        }
        //If the Name of the Author is Empty
        if (book.getAuthor().isEmpty()) {
            throw new LibBusinessException("802", "The Author's Name can't be Empty");
        }

        //If the Price is less than 10
        if (book.getPrice().intValue() < 10) {
            throw new LibBusinessException("803", "The Price of the Book must be $10 or greater than $10");
        }
        return bookRepository.save(book);
    }

}

Enter fullscreen mode Exit fullscreen mode

The LibBusinessException

package com.bukhari.exceptionhandlingvalidation.classexception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class LibBusinessException extends RuntimeException {

    private String errorCode;
    private String errorMessage;

    public LibBusinessException(String errorCode, String errorMessage) {
        super();
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
}
Enter fullscreen mode Exit fullscreen mode

The BookController

package com.bukhari.exceptionhandlingvalidation.controller;

import com.bukhari.exceptionhandlingvalidation.classexception.LibBusinessException;
import com.bukhari.exceptionhandlingvalidation.entity.Book;
import com.bukhari.exceptionhandlingvalidation.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("digital/lib")
public class LibraryController {
    @Autowired
    private BookService bookService;

    @PostMapping("/book")
    public ResponseEntity<?> saveBook(@RequestBody Book book) {
        try {
            Book savedBook = bookService.addBook(book);
            return new ResponseEntity<Book>(savedBook, HttpStatus.CREATED);
        } catch (LibBusinessException e) {
            LibBusinessException libex = new LibBusinessException(e.getErrorCode(), e.getErrorMessage());
            return new ResponseEntity<LibBusinessException>(libex, HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            LibBusinessException libex = new LibBusinessException("804", "Something went wrong in controller");
            return new ResponseEntity<LibBusinessException>(libex, HttpStatus.BAD_REQUEST);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

If the Book Name is Empty

PostMan Request and Response

Request and Response

If the Book Author Name is Empty

PostMan Request and Response

Request and Response

If the Book Price is less than $10

PostMan Request and Response

Request and Response

If the Book entity is null
Request Response

Validation and Global Exception Handling Using ControllerAdvice

Let's Understand now validation and Global Exception Handling
We will take AppUser Entity for this purpose. (pom.xml is same).

The AppUser Entity

package com.bukhari.exceptionhandlingvalidation.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class AppUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int userId;
    private String name;
    private String email;
    private String mobile;
    private String gender;
    private int age;
    private String nationality;

    public AppUser() {
    }

    public AppUser(int userId, String name, String email, String mobile, String gender, int age, String nationality) {
        this.userId = userId;
        this.name = name;
        this.email = email;
        this.mobile = mobile;
        this.gender = gender;
        this.age = age;
        this.nationality = nationality;
    }

    public int getUserId() {
        return userId;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

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

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }
}

Enter fullscreen mode Exit fullscreen mode

Request For AppUser

package com.bukhari.exceptionhandlingvalidation.request;

import jakarta.validation.constraints.*;

/**
 * Global Exception Handling AppUserRequest
 */
public class AppUserRequest {
    @NotNull(message = "username must not be null")
    private String name;
    @Email(message = "Invalid email address, Please provide a valid email")
    private String email;
    @Pattern(regexp = "^\\d{10}$", message = "Invalid mobile number, Please Provide a Valid Number")
    private String mobile;
    private String gender;
    @Min(value = 18, message = "Age must be 18 or greater")
    @Max(value = 60, message = "Age must be 60 or less")
    private int age;
    @NotBlank
    private String nationality;

    public AppUserRequest(String name, String email, String mobile, String gender, int age, String nationality) {
        this.name = name;
        this.email = email;
        this.mobile = mobile;
        this.gender = gender;
        this.age = age;
        this.nationality = nationality;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

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

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }
}


Enter fullscreen mode Exit fullscreen mode

We are going to validate and handle the following exceptions

  1. Name must not be null.
  2. Email must be valid.
  3. Mobile Number must be valid.
  4. Age must be between 18 and 60.
  5. Nationality must not be blank (non-empty and non-null).
  6. Map Custom Class to ExceptionHandler if user is not Found.

For this purpose we are using @RestControllerAdvice as Global Exception handler which is a better approach as compared to the Class Level approach. The @RestControllerAdvice is mapped to all the Controllers and once the request is triggered the Advice checks for any mapping matches to it and sends the response accordingly. Since Spring Provides us @ExceptionHandler annotation to intercept any java as well as logical exception so we can implement according to our own requirement.

The RestControllerAdvice

package com.bukhari.exceptionhandlingvalidation.globalexception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
 * Global Exception Handling ControllerAdvice
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleInvalidArgument(MethodArgumentNotValidException ex) {
        Map<String, String> errorMap = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            errorMap.put(error.getField(), error.getDefaultMessage());
        });
        return errorMap;
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(UserNotFoundException.class)
    public Map<String, String> handleBusinessException(UserNotFoundException ex) {
        Map<String, String> errorMap = new HashMap<>();
        errorMap.put("errorMessage", ex.getMessage());
        return errorMap;
    }

}

Enter fullscreen mode Exit fullscreen mode

The Custom Class For UserNotFoundException

package com.bukhari.exceptionhandlingvalidation.globalexception;

/**
 * Global Exception Handling UserNotFoundException
 */
public class UserNotFoundException  extends Exception{
    public UserNotFoundException(String message) {
        super(message);
    }
}

Enter fullscreen mode Exit fullscreen mode

The AppUserRepository

package com.bukhari.exceptionhandlingvalidation.repository;

import com.bukhari.exceptionhandlingvalidation.entity.AppUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
 * Global Exception Handling AppUserRepository
 */
@Repository
public interface AppUserRepository extends JpaRepository<AppUser,Integer> {

}

Enter fullscreen mode Exit fullscreen mode

The AppUserService

package com.bukhari.exceptionhandlingvalidation.service;

import com.bukhari.exceptionhandlingvalidation.entity.AppUser;
import com.bukhari.exceptionhandlingvalidation.globalexception.UserNotFoundException;
import com.bukhari.exceptionhandlingvalidation.repository.AppUserRepository;
import com.bukhari.exceptionhandlingvalidation.request.AppUserRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
 * Global Exception Handling AppUserService
 */
@Service
public class AppUserService {

    @Autowired
    private AppUserRepository appUserRepository;


    public AppUser saveUser(AppUserRequest userRequest) {
        AppUser user = new AppUser(0, userRequest.getName(), userRequest.getEmail(),
                userRequest.getMobile(), userRequest.getGender(), userRequest.getAge(), userRequest.getNationality());
        return appUserRepository.save(user);
    }


    public List<AppUser> getALlUsers() {
        return appUserRepository.findAll();
    }


    public AppUser getUser(int id) throws UserNotFoundException {
        Optional<AppUser> optional = appUserRepository.findById(id);
        if (optional.isPresent()) {
            return optional.get();
        } else {
            throw new UserNotFoundException("user not found with id : " + id);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

The AppUserController

package com.bukhari.exceptionhandlingvalidation.controller;


import com.bukhari.exceptionhandlingvalidation.entity.AppUser;
import com.bukhari.exceptionhandlingvalidation.globalexception.UserNotFoundException;
import com.bukhari.exceptionhandlingvalidation.request.AppUserRequest;
import com.bukhari.exceptionhandlingvalidation.service.AppUserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class AppUserController {

    @Autowired
    private AppUserService appUserService;

    @PostMapping("/signup")
    public ResponseEntity<AppUser> saveUser(@RequestBody @Valid AppUserRequest userRequest){
        return new ResponseEntity<>(appUserService.saveUser(userRequest), HttpStatus.CREATED);
    }

    @GetMapping("/fetchAll")
    public ResponseEntity<List<AppUser>> getAllUsers(){
        return ResponseEntity.ok(appUserService.getALlUsers());
    }

    @GetMapping("/{id}")
    public ResponseEntity<AppUser> getUser(@PathVariable int id) throws UserNotFoundException {
        return ResponseEntity.ok(appUserService.getUser(id));
    }
}

Enter fullscreen mode Exit fullscreen mode

The @Valid annotation is used to validate the payload

PostMan Request and Response are given below.

If the data is not valid

Request and Response

If the User is not Found.

Request and Response

Hope the Tutorial was helpful. You learnt something from it. If it was helpful make sure to follow and like.

Top comments (0)