Global Exception Handling in the Spring Boot Rest API

Last updated on October 4th, 2024

In the Spring Boot Application, if we need to handle exceptions globally, we can define a class that is annotated with @ControllerAdvice annotation and define methods in that class which is annotated with @ExceptionHandler annotation. In this topic, how to use @ControllerAdvice and @ExceptionHandler annotation to use global exception handling in Spring Boot Rest API.

global_exception_handling_in_the_spring_boot_rest_api

We will create a CRUD restful web service example to implement Global Exception handling in Spring Boot Application using Spring Web, Maven, Spring Data JPA and MySql.

These are the following steps:

1. Create a Spring Boot Starter Project
2. Keep Eclipse IDE ready
3. Defining Configuration
4. Creating a JPA Entity class
5. Creating a JPA Repository
6. Creating an Error Model class
7. Creating a Custom Exception class
8. Creating a Global Exception Handler class
9. Creating a Service
10. Creating a Rest Controller class
11. Run the Project

1. Create a Spring Boot Starter Project

We are creating a Spring Boot Application from the web tool Spring Initializr or you can create it from the IDE(STS, VS Code etc.) you are using. 
Add the following dependencies:

  • Spring Web
  • Spring Data JPA
  • Mysql Driver

2. Keep the IDE ready

We are importing this created application into our Eclipse IDE or you can import it into another IDE you are using. You can refer to this article to create and set up the Spring Boot Project in Eclipse IDE.

Project Structure of Global Exception Handling in Spring Boot Rest API

global_exception_handling_in_the_spring_boot_rest_api

Maven Dependency

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>2.3.0.RELEASE</version>
		<relativePath/>
		<!-- lookup parent from repository -->
	</parent>
	<groupId>com.poc</groupId>
	<artifactId>Global_Exception_Handle_Spring_Boot_Rest_Api</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>Global_Exception_Handle_Spring_Boot_Rest_Api</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</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-web</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

3. Defining Configuration

We are configuring the MySql database configuration in the application.properties file.
application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/user_db?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true

4. Creating a JPA Entity class

We are creating a JPA entity class User with properties(id, userName, mobileNo, emailId, city, password).
User.java

package com.poc.rest.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity

public class User {

  @Id

  @GeneratedValue(strategy = GenerationType.IDENTITY)

  private Integer id;

  private String userName;

  private String mobileNo;

  private String emailId;

  private String city;

  private String password;

  public User() {

  }

  public Integer getId() {

    return id;

  }

  public void setId(Integer id) {

    this.id = id;

  }

  public String getUserName() {

    return userName;

  }

  public void setUserName(String userName) {

    this.userName = userName;

  }

  public String getMobileNo() {

    return mobileNo;

  }

  public void setMobileNo(String mobileNo) {

    this.mobileNo = mobileNo;

  }

  public String getEmailId() {

    return emailId;

  }

  public void setEmailId(String emailId) {

    this.emailId = emailId;

  }

  public String getCity() {

    return city;

  }

  public void setCity(String city) {

    this.city = city;

  }

  public String getPassword() {

    return password;

  }

  public void setPassword(String password) {

    this.password = password;

  }
}

5. Creating a JPA Repository

We are a JPA Repository to communicates with a database table and performs persistence operations with query methods(save(), findById(), findAll(), and delete()).
UserRespository.java

package com.poc.rest.repo;

import org.springframework.data.jpa.repository.JpaRepository;

import com.poc.rest.entity.User;

public interface UserRepstry extends JpaRepository < User, Integer > {

}

6. Creating an Error Model class

We are creating an error model class ErrorDetail.
ErrorDetails.java

package com.poc.rest.exception;

public class ErrorDetails {

  private Integer status;

  private String message;

  public ErrorDetails(Integer status, String message) {

    super();

    this.status = status;

    this.message = message;

  }

  public Integer getStatus() {

    return status;

  }

  public String getMessage() {

    return message;

  }
}

7. Creating a Custom Exception class

We are creating a custom exception class UserNotFoundException and this class is annotated with @ResponseStatus annotation.
UserNotFoundException.java

package com.poc.rest.exception;

import org.springframework.http.HttpStatus;

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

@ResponseStatus(value = HttpStatus.NOT_FOUND)

public class UserNotFoundException extends RuntimeException {

  public UserNotFoundException(String message) {

    super(message);

  }
}

→ @ResponseStatus: Marks a method or exception class with the status code() and reason() that should be returned. A status code is applied to the HTTP response when the handler method is invoked and overrides status information detail set by the other means, like ResponseEntity or “redirect:”.

8. Creating a Global Exception Handler class

 We are creating a global exception handler class GlobalExceptionHandler and this class is annotated with @ControllerAdvice and extends the ResponseEntityExceptionHandler class.

package com.poc.rest.exception;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice

public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(UserNotFoundException.class)

  public ResponseEntity < ErrorDetails > userNotFoundException(UserNotFoundException ex) {

    ErrorDetails errorModel = new ErrorDetails(0, ex.getMessage());

    return new ResponseEntity < ErrorDetails > (errorModel, HttpStatus.NOT_FOUND);

  }

  @ExceptionHandler(Exception.class)

  public ResponseEntity < ? > globleExcpetionHandler(Exception ex) {

    ErrorDetails errorModel = new ErrorDetails(0, ex.getMessage());

    return new ResponseEntity < > (errorModel, HttpStatus.INTERNAL_SERVER_ERROR);

  }
}

→ @ControllerAdvice is a specialization of the @Component annotation which authorizes to handling of exceptions across the whole application in one global exception-handling component. It can act as an interceptor of exceptions that are thrown by methods.
→ ResponseEntityExceptionHandler is a convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through the @ExceptionHandler methods of our application. This base class provides an @ExceptionHandler method for handling internal Spring MVC exceptions that are raised on the application. This method returns a ResponseEntity for writing to the response with a message converter, in contrast to DefaultHandlerExceptionResolver which returns the output as the ModelAndView.
→ @ExceptionHandler is an annotation that is used for handling exceptions in the specific handler classes or the handler methods. 

9. Creating a Service

We are creating a service interface UserService with some method declaration.
UserService.java

package com.poc.rest.service;

import java.util.List;

import com.poc.rest.entity.User;

public interface UserService {

  public List < User > getTheUsersList();

  public void save(User user);

  public User findById(Integer id);

  public void delete(User user);

}

UserServiceImpl.java

package com.poc.rest.service;

import java.util.List;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import com.poc.rest.entity.User;

import com.poc.rest.exception.UserNotFoundException;

import com.poc.rest.repo.UserRepstry ;

@Service

public class UserServiceImpl implements UserService {

  @Autowired

  private UserRepstry  userRepo;

  @Override

  public List < User > getTheUsersList() {

    return userRepo.findAll();

  }

  @Override

  public void save(User user) {

    userRepo.save(user);

  }

  @Override

  public User findById(Integer id) {

    Optional < User > userInfo = userRepo.findById(id);

    User user = null;

    if (userInfo.isPresent()) {

      user = userInfo.get();

    } else {

      throw new UserNotFoundException("The user info is not available:" + id);

    }

    return user;

  }

  @Override

  public void delete(User user) {

    userRepo.delete(user);

  }
}

10. Creating a Rest Controller class

We are creating a Rest Controller class UserController to handler requests from clients and give the response in JSON form.
UserController.java

package com.poc.rest.controller;

import java.util.LinkedHashMap;

import java.util.List;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.DeleteMapping;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.PutMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import com.poc.rest.entity.User;

import com.poc.rest.service.UserService;

@RestController

@RequestMapping("/api")

public class UserController {

  @Autowired

  private UserService userService;

  @GetMapping("/users")

  public ResponseEntity < ? > getUser() {

    Map < String, Object > respJsonOutput = new LinkedHashMap < String, Object > ();

    List < User > userList = userService.getTheUsersList();

    if (!userList.isEmpty()) {

      respJsonOutput.put("status", 1);

      respJsonOutput.put("data", userList);

      return new ResponseEntity < > (respJsonOutput, HttpStatus.OK);

    } else {

      respJsonOutput.clear();

      respJsonOutput.put("status", 0);

      respJsonOutput.put("message", "Data is not found");

      return new ResponseEntity < > (respJsonOutput, HttpStatus.NOT_FOUND);

    }

  }

  @PostMapping("/save")

  public ResponseEntity < ? > saveUser(@RequestBody User user) {

    Map < String, Object > respJsonOutput = new LinkedHashMap < String, Object > ();

    userService.save(user);

    respJsonOutput.put("status", 1);

    respJsonOutput.put("message", "Record is Saved Successfully!");

    return new ResponseEntity < > (respJsonOutput, HttpStatus.CREATED);

  }

  @GetMapping("/user/{id}")

  public ResponseEntity < ? > getUserById(@PathVariable Integer id) {

    Map < String, Object > respJsonOutput = new LinkedHashMap < String, Object > ();

    User user = userService.findById(id);

    respJsonOutput.put("status", 1);

    respJsonOutput.put("data", user);

    return new ResponseEntity < > (respJsonOutput, HttpStatus.OK);

  }

  @DeleteMapping("/delete/{id}")

  public ResponseEntity < ? > deleteUser(@PathVariable Integer id) {

    Map < String, Object > respJsonOutput = new LinkedHashMap < String, Object > ();

    User user = userService.findById(id);

    userService.delete(user);

    respJsonOutput.put("status", 1);

    respJsonOutput.put("message", "Record is deleted successfully!");

    return new ResponseEntity < > (respJsonOutput, HttpStatus.OK);

  }

  @PutMapping("/update/{id}")

  public ResponseEntity < ? > updateTheUser(@PathVariable Integer id, @RequestBody User userDetail) {

    Map < String, Object > respJsonOutput = new LinkedHashMap < String, Object > ();

    User user = userService.findById(id);

    user.setUserName(userDetail.getUserName());

    user.setMobileNo(userDetail.getMobileNo());

    user.setEmailId(userDetail.getEmailId());

    user.setCity(userDetail.getCity());

    user.setPassword(userDetail.getPassword());

    userService.save(user);

    respJsonOutput.put("status", 1);

    respJsonOutput.put("data", userService.findById(id));

    return new ResponseEntity < > (respJsonOutput, HttpStatus.OK);

  }

}

11. Run the Project

Right-click on the project’s SpringBootApplication class then click on Run as Java Application

Test API in the Postman tool

POST: http://localhost:8080/api/save

global_exception_handling_in_the_spring_boot_rest_api

GET: http://localhost:8080/api/users

global_exception_handling_in_the_spring_boot_rest_api

GET: http://localhost:8080/api/user/4

global_exception_handling_in_the_spring_boot_rest_api

PUT: http://localhost:8080/api/update/4

global_exception_handling_in_the_spring_boot_rest_api

DELETE: http://localhost:8080/api/delete/4

global_exception_handling_in_the_spring_boot_rest_api

There is no user with id 9.
GET Type: http://localhost:8080/api/user/9

global_exception_handling_in_the_spring_boot_rest_api

Global exception handles the exception where id is not available.

Conclusion


This example explains How to customize the exception and return a JSON response. How to create a Global Exception Handler class? What is a @ControllerAdvice annotation? What is a @ExceptionHandler annotation? What is a @ResponseStatus?

1 thought on “Global Exception Handling in the Spring Boot Rest API”

Leave a Comment