Spring Boot Exception Handling for REST APIs

In this article, we will learn how to handle exceptions for RESTful Web Services developed using Spring Boot.
We can handle exceptions in REST API in the same way we handle them in the SpringMVC-based web application—by using the @ExceptionHandler and @ControllerAdvice annotations. Instead of rendering a view, you can return ResponseEntity with the appropriate HTTP status code and exception details.
By default, Spring Boot provides a /error mapping that handles all errors in a sensible way, and it is registered as a “global” error page in the servlet container. Rest clients, it produces a JSON response with details of the error, the HTTP status, and the exception message.
Instead of simply throwing an exception with the HTTP status code, it is better to provide more details about the issue, such as the error code, message, cause, etc. In this example, we define a class annotated with @ControllerAdvice to customize the JSON document to return for a particular controller and/or exception type.
Let's develop CRUD REST APIs for the Employee resource using Spring Boot, Spring Data JPA (Hibernate), and MySQL database and we will look into exception handling for these REST services.

Table of Contents

  1. What we'll build?
  2. Tools and Technologies Used
  3. Creating and Importing a Project
  4. The pom.xml File
  5. Create JPA Entity - Employee.java
  6. Create Spring JPA Repository - EmployeeRepository.java
  7. Create Spring Rest Controller - EmployeeController.java
  8. Exception(Error) Handling for RESTful Services
  9. Running Application via Application.java
  10. Testing via Postman Client
  11. Source code on GitHub

1. What we'll build?

We will develop a simple Spring Boot RESTful CRUD APIs for Employee resource and we will implement Exception(Error) Handling for these RESTful Services.

2. Tools and Technologies Used

  • Spring Boot - 3+
  • JDK - 17 or later
  • Spring Framework - 6+
  • Hibernate - 6+
  • Maven - 3+
  • MySQL
  • IDE - Eclipse or Spring Tool Suite (STS)

3. Create and Set up Spring Boot Project

There are many ways to create a Spring Boot application. The simplest way is to use Spring Initializr at http://start.spring.io/, which is an online Spring Boot application generator.

4. The pom.xml File

Refer to the below pom.xml for your reference.
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>net.guides.springboot2</groupId>
	<artifactId>springboot2-jpa-crud-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springboot2-jpa-crud-example</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.4</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<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-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</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 next step is to create JPA Entity - Employee.java

5. Create JPA Entity - Employee.java

package net.guides.springboot2.springboot2jpacrudexample.model;

import jakarta.persistence.*;

@Entity
@Table(name = "employees")
public class Employee {

    private long id;
    private String firstName;
    private String lastName;
    private String emailId;
 
    public Employee() {
  
    }
 
    public Employee(String firstName, String lastName, String emailId) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.emailId = emailId;
    }
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
 
    @Column(name = "first_name", nullable = false)
       public String getFirstName() {
           return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    @Column(name = "last_name", nullable = false)
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    @Column(name = "email_address", nullable = false)
    public String getEmailId() {
        return emailId;
    }
    public void setEmailId(String emailId) {
        this.emailId = emailId;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", emailId=" + emailId
        + "]";
    }
}
Next, we will create a Spring Data JPA repository - EmployeeRepository.java

6. Create Spring JPA Repository - EmployeeRepository.java

package net.guides.springboot2.springboot2jpacrudexample.model.repository;

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

import net.guides.springboot2.springboot2jpacrudexample.model.Employee;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{

}
Once the entity and repository are in place then we will create Spring Rest Controller - EmployeeController.java

7. Create Spring Rest Controller - EmployeeController.java

package net.guides.springboot2.springboot2jpacrudexample.model.controller;

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

import jakarta.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
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 net.guides.springboot2.springboot2jpacrudexample.model.Employee;
import net.guides.springboot2.springboot2jpacrudexample.model.exception.ResourceNotFoundException;
import net.guides.springboot2.springboot2jpacrudexample.model.repository.EmployeeRepository;

@RestController
@RequestMapping("/api/v1")
public class EmployeeController {
    @Autowired
    private EmployeeRepository employeeRepository;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @GetMapping("/employees/{id}")
    public ResponseEntity<Employee> getEmployeeById(@PathVariable(value = "id") Long employeeId)
        throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
        .orElseThrow(() -> new ResourceNotFoundException("Employee not found for this id :: " + employeeId));
        return ResponseEntity.ok().body(employee);
    }

    @PostMapping("/employees")
    public Employee createEmployee(@Valid @RequestBody Employee employee) {
        return employeeRepository.save(employee);
    }

    @PutMapping("/employees/{id}")
    public ResponseEntity<Employee> updateEmployee(@PathVariable(value = "id") Long employeeId,
         @Valid @RequestBody Employee employeeDetails) throws ResourceNotFoundException {
         Employee employee = employeeRepository.findById(employeeId)
        .orElseThrow(() -> new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

        employee.setEmailId(employeeDetails.getEmailId());
        employee.setLastName(employeeDetails.getLastName());
        employee.setFirstName(employeeDetails.getFirstName());
        final Employee updatedEmployee = employeeRepository.save(employee);
        return ResponseEntity.ok(updatedEmployee);
    }

    @DeleteMapping("/employees/{id}")
    public Map<String, Boolean> deleteEmployee(@PathVariable(value = "id") Long employeeId)
        throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
        .orElseThrow(() -> new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

       employeeRepository.delete(employee);
       Map<String, Boolean> response = new HashMap<>();
       response.put("deleted", Boolean.TRUE);
       return response;
    }
}
So far we have developed CRUD RESTful APIs for the Employee resource. Now we will look into how to handle exceptions or errors for the above RESTFul APIs.

8. Exception(Error) Handling for RESTful Services

Spring Boot provides a good default implementation for exception handling for RESTful Services.
Let’s quickly look at the default Exception Handling features provided by Spring Boot.

Resource Not Present

Here's what happens when you fire a request to a resource not found: http://localhost:8080/some-dummy-url
{
  "timestamp": 1512713804164,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/some-dummy-url"
}
That's a cool error response. It contains all the details that are typically needed.

What happens when we throw an Exception?

Let’s see what Spring Boot does when an exception is thrown from a Resource. we can specify the Response Status for a specific exception along with the definition of the Exception with @ResponseStatus annotation.
Let's create a ResourceNotFoundException.java class.
package com.companyname.springbootcrudrest.exception;

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

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception{

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message){
        super(message);
    }
}

Customizing Error Response Structure

The default error response provided by Spring Boot contains all the details that are typically needed.
However, you might want to create a framework-independent response structure for your organization. In that case, you can define a specific error response structure.
Let’s define a simple error response bean.
package com.companyname.springbootcrudrest.exception;

import java.util.Date;

public class ErrorDetails {
    private Date timestamp;
    private String message;
    private String details;

    public ErrorDetails(Date timestamp, String message, String details) {
        super();
        this.timestamp = timestamp;
        this.message = message;
        this.details = details;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public String getMessage() {
        return message;
   }

    public String getDetails() {
       return details;
   }
}

Create GlobalExceptionHandler class

To use ErrorDetails to return the error response, let’s create a GlobalExceptionHandler class annotated with @ControllerAdvice annotation. This class handles exception-specific and global exceptions in a single place.
package com.companyname.springbootcrudrest.exception;

import java.util.Date;

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.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> globleExcpetionHandler(Exception ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
We are done with developing CRUD REST APIs and exception handling for REST APIs. Now it's time to run this Spring boot application.

9. Running Application via Application.java

This spring boot application has an entry point Java class called Application.java with the public static void main(String[] args) method, which you can run to start the application.
package net.guides.springboot2.springboot2jpacrudexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

10. Testing via Postman Client



Note that in the above screenshot, we have done exception handling successfully with an expected response from REST APIs.

11. Source code on GitHub

The source code of this article is available on my GitHub Repository:

Comments

Post a Comment

Leave Comment