Exception Handling in Spring Boot Application

Table of Contents

Introduction

This post will show you an example on exception handling in Spring web application. In other words, you can also say exception handling in Spring MVC. I will use handle exception on Spring REST APIs but not limited to Spring REST APIs. You can create Spring MVC application and handle the exception same way. I will use @ControllerAdvice and @ExceptionHandler annotations to handle exception in Spring application.

Related Posts:

Prerequisites

Java 1.8+ (11), Gradle 4.10.2, Spring Boot 2.1.5/2.6.6, Maven 3.8.5

Project Setup

Create a gradle based project in Eclipse, the project name is spring-web-exception-handling.

For maven based project, use the following pom.xml file or you can update according to your needs.

<?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>com.roytuts</groupId>
	<artifactId>spring-web-exception-handling</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>11</maven.compiler.source>
		<maven.compiler.target>11</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.6</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
	</dependencies>

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

Update the default generated build script with the following content if you are using gradle based project.

I will use in-memory database h2 to do quick PoC. Using in-memory database you don’t need to define datasource, you don’t need to define table creation SQL script. The entity class will create the required table in the h2 database.

I am using Spring Data JPA to get benefits of built-in query out of the box using Java methods.

buildscript {
	ext {
		springBootVersion = '2.1.5.RELEASE'
	}
    repositories {
    	mavenLocal()
    	mavenCentral()
    }
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
	mavenLocal()
    mavenCentral()
}
dependencies {
	compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
	compile("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
	runtime("com.h2database:h2:1.4.196")
}

Entity Class

You need to create entity class to perform database operations. This class will map the Java fields to table columns.

In the below entity class I am using auto-generated primary key. The table column name will be same as the Java class attribute unless you mention the column name using @Column annotation.

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Employee {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	@Column(name = "email_address")
	private String email;
	public Employee() {
	}
	public Employee(Integer id, String name, String email) {
		this.id = id;
		this.name = name;
		this.email = email;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	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;
	}
}

Repository Interface

I am using Spring Data JPA repository, so I need to create an interface for query the database.

As you know, Spring Data JPA provides API for querying data out of the box by using built-in methods or using custom methods, so I am creating below two methods to find out employee by name or email address.

import org.springframework.data.jpa.repository.JpaRepository;
import com.roytuts.spring.web.exception.handling.entity.Employee;
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
	Employee findByName(String name);
	Employee findByEmail(String email);
}

Exception Classes

I will create custom exception classes to throw exception when an exception is meant to throw from the application. There are different types of exception can be thrown based on the error scenario.

By creating the custom exception class I am also showing the nice message to the end users instead of default 4xx/5xx message.

Application exception can be used to throw application specific exception:

public class AppException extends Exception {

	private static final long serialVersionUID = 1L;

	private String code;

	public AppException(String message) {
		super(message);
		this.code = null;
	}

	public AppException(Throwable cause) {
		super(cause);
		this.code = null;
	}

	public AppException(String message, String code) {
		super(message);
		this.code = code;
	}

	public AppException(String message, Throwable cause) {
		super(message, cause);
		this.code = null;
	}

	public String getCode() {
		return code;
	}

}

Database related exception can be thrown using the following class. You can create as many exception classes as you need for your application.

public class DbException extends Exception {

	private static final long serialVersionUID = 1L;

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

	public DbException(String message, Throwable cause) {
		super(message, cause);
	}

}

Advice For Exception

When an employee for the given id, or name, or email is not found then an exception is thrown, this extra Spring MVC configuration is used to render an HTTP 404:

A controller advice (@ControllerAdvice) allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation driven interceptor.

Any class annotated with @ControllerAdvice becomes a controller-advice and three types of method are supported:

  • Exception handling methods annotated with @ExceptionHandler.
  • Model enhancement methods (for adding additional data to the model) annotated with
  • @ModelAttribute. Note that these attributes are not available to the exception handling views.
  • Binder initialization methods (used for configuring form-handling) annotated with @InitBinder.

@ExceptionHandler configures the advice to only respond if an exception is thrown.

import java.util.Objects;

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.multipart.MultipartException;

import com.roytuts.spring.web.exception.handling.constants.AppConstants;
import com.roytuts.spring.web.exception.handling.exception.AppException;
import com.roytuts.spring.web.exception.handling.exception.DbException;
import com.roytuts.spring.web.exception.handling.wrapper.ExceptionWrapper;

@ControllerAdvice
public class AppExceptionHandler {

	@ExceptionHandler(AppException.class)
	public ResponseEntity<ExceptionWrapper> appException(AppException exception) {
		if (!Objects.isNull(exception.getCode()) && AppConstants.SUCCESS.equalsIgnoreCase(exception.getCode())) {
			ExceptionWrapper exceptionWrapper = new ExceptionWrapper(HttpStatus.OK.toString(), exception.getMessage());

			return new ResponseEntity<ExceptionWrapper>(exceptionWrapper, HttpStatus.OK);
		}

		ExceptionWrapper exceptionWrapper = new ExceptionWrapper(HttpStatus.INTERNAL_SERVER_ERROR.toString(),
				exception.getMessage());

		return new ResponseEntity<ExceptionWrapper>(exceptionWrapper, HttpStatus.INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler(DbException.class)
	public ResponseEntity<ExceptionWrapper> dbException(DbException exception) {
		if (!Objects.isNull(exception.getMessage())
				&& AppConstants.INCORRECT_RESULT_SIZE.equalsIgnoreCase(exception.getMessage())) {
			ExceptionWrapper exceptionWrapper = new ExceptionWrapper(HttpStatus.OK.toString(), exception.getMessage());

			return new ResponseEntity<ExceptionWrapper>(exceptionWrapper, HttpStatus.OK);
		}

		ExceptionWrapper exceptionWrapper = new ExceptionWrapper(HttpStatus.INTERNAL_SERVER_ERROR.toString(),
				exception.getMessage());

		return new ResponseEntity<ExceptionWrapper>(exceptionWrapper, HttpStatus.INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler(IllegalArgumentException.class)
	public ResponseEntity<ExceptionWrapper> validationException(IllegalArgumentException exception) {
		ExceptionWrapper exceptionWrapper = new ExceptionWrapper(HttpStatus.BAD_REQUEST.toString(),
				exception.getMessage());

		return new ResponseEntity<ExceptionWrapper>(exceptionWrapper, HttpStatus.BAD_REQUEST);
	}

	@ExceptionHandler(MultipartException.class)
	public ResponseEntity<ExceptionWrapper> multipartException(MultipartException exception) {
		ExceptionWrapper exceptionWrapper = new ExceptionWrapper(HttpStatus.INTERNAL_SERVER_ERROR.toString(),
				exception.getMessage());

		return new ResponseEntity<ExceptionWrapper>(exceptionWrapper, HttpStatus.INTERNAL_SERVER_ERROR);
	}

	@ExceptionHandler(Error.class)
	public ResponseEntity<ExceptionWrapper> otherError(Error error) {
		ExceptionWrapper exceptionWrapper = new ExceptionWrapper(HttpStatus.NOT_FOUND.toString(), error.getMessage());

		return new ResponseEntity<ExceptionWrapper>(exceptionWrapper, HttpStatus.NOT_FOUND);
	}

}

The body of the advice generates the content. In this case, it gives the error code and message of the exception.

I have defined different exception handler through different methods though this example will not show use of all of them, but it shows how you can handle different exception scenarios.

Spring REST Controller

Create below Spring REST controller to wrap your repository into web layer.

I am using here only @GetMapping, if you want other http request mapping, you can use.

@RestController indicates that the data returned by each method will be written straight into the response body instead of rendering a template.

An EmployeeRepository is injected by constructor into the controller.

The appropriate exception class is used to indicate when an employee is looked up but not found. Even an appropriate exception class is used when you are trying to save new employee information without providing employee’s name or email.

import java.util.List;
import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.roytuts.spring.web.exception.handling.entity.Employee;
import com.roytuts.spring.web.exception.handling.repository.EmployeeRepository;

@RestController
public class EmployeeRestController {

	@Autowired
	private EmployeeRepository employeeRepository;

	@GetMapping("/employees")
	public ResponseEntity<List<Employee>> getEmployees() {
		return new ResponseEntity<List<Employee>>(employeeRepository.findAll(), HttpStatus.OK);
	}

	@GetMapping("/employee/id/{id}")
	public ResponseEntity<Employee> getEmployeeById(@PathVariable Integer id) {
		Employee employee = employeeRepository.findById(id)
				.orElseThrow(() -> new Error("Employee not found for the given id " + id));

		return new ResponseEntity<Employee>(employee, HttpStatus.OK);
	}

	@GetMapping("/employee/name/{name}")
	public ResponseEntity<Employee> getEmployeeByName(@PathVariable String name) {
		Employee employee = employeeRepository.findByName(name);

		if (employee == null) {
			throw new Error("Employee not found for the given name " + name);
		}

		return new ResponseEntity<Employee>(employee, HttpStatus.OK);
	}

	@GetMapping("/employee/email/{email}")
	public ResponseEntity<Employee> getEmployeeByEmail(@PathVariable String email) {
		Employee employee = employeeRepository.findByEmail(email);

		if (employee == null) {
			throw new Error("Employee not found for the given email " + email);
		}

		return new ResponseEntity<Employee>(employee, HttpStatus.OK);
	}

	@PostMapping("/employee/save")
	public ResponseEntity<Void> saveNewEmployee(@RequestBody Employee employee) {
		if (Objects.isNull(employee.getName()) || Objects.isNull(employee.getEmail())) {
			throw new IllegalArgumentException("Invalid request");
		}

		return new ResponseEntity<Void>(HttpStatus.CREATED);
	}

}

Spring Boot Main Class

In Spring Boot application, a public static void main method and @SpringBootApplication annotation are enough to launch the application.


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EntityScan("com.roytuts.spring.web.exception.handling.entity")
@EnableJpaRepositories("com.roytuts.spring.web.exception.handling.repository")
@SpringBootApplication
public class SpringWebExceptionHandlingApp {

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

}

Testing the Exception Handling

Run the above main class to deploy the application into embedded Tomcat server.

Hit the URL http://localhost:8080/employees, you will see below output:

exception handling in spring mvc

Hit the URL http://localhost:8080/employee/id/1, you will see below output:

exception in spring boot app

Hit the URL http://localhost:8080/employee/id/8, you will see below output:

exception in spring web

Similarly you can test for a given email or name. For example, if you try to save the employee information without giving name or email as input then you will see the following error:

exception handling in spring boot rest app

Therefore, in the above test results you can see that the appropriate exception class was called based on their exceptions.

Source Code

Download

2 thoughts on “Exception Handling in Spring Boot Application

Leave a Reply

Your email address will not be published. Required fields are marked *