Exception Handling and Validation in Spring boot
Tags: springboot, exceptionhandling, validation, java
Published: 2023-03-15 10:46:37

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](https://start.spring.io/)**


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>
```

**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
```

## Class Level Exception Handling

The Hierarchy of Code

![The Hierarchy of java files in an IDE](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fh90n33rybrqr13sjgns.png)


**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;
    }
}

```
**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> {

}
```

**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);
    }

}

```
**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;
    }
}
```

**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);
        }
    }
}

```

**If the Book Name is Empty**

PostMan Request and Response


![Request and Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3ujkqfrpjo2sp63brmb.png)

**If the Book Author Name is Empty**

PostMan Request and Response

![Request and Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqqvbxw03nsuuddz1ec2.png)

**If the Book Price is less than $10**

PostMan Request and Response

![Request and Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ckrqz3ykhlt279wxc33l.png)

**If the Book entity is null**
![Request Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wlaewu7zmxom583rlqf4.png)

## 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;
    }
}

```

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;
    }
}


```

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;
    }

}

```
**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);
    }
}

```

**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> {

}

```

**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);
        }
    }
}

```

**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));
    }
}

```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9zmaoe3fr9xmqjmtfcy.png)

**If the User is not Found.**


![Request and Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppvkj3d0tc8ik7kzt2s4.png)


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