Spring MVC @ControllerAdvice Annotation with Example





Hey guys in this post, we will discuss @ControllerAdvice annotation with an example.

Overview


@ControllerAdvice used for global error handling in the Spring MVC application. It also has full control over the body of the response and the status code. It means that we can have a centralized way to handle exceptions, binding, etc. it applies to all the defined controllers.

@ControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(MyException.class) 
    public ResponseEntity<String> reponseMyException(Exception e) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("my message");
    }
}

Watch the video


Example on @ControllerAdvice


The best way to understand @ControllerAdvice annotation is by creating an example

Create spring boot project


There are many different ways to create a spring boot application, you can follow the below articles to create one –

>> Create spring boot application using Spring initializer
>> Create spring boot application in Spring tool suite [STS]
>> Create spring boot application in IntelliJ IDEA

Add the maven dependencies


Open pom.xml and add the following dependencies –

<?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.4.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>in.bushansirgur</groupId>
	<artifactId>controlleradvice</artifactId>
	<version>v1</version>
	<name>controlleradvice</name>
	<description>Spring boot controller advice</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<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>
			<scope>runtime</scope>
			<optional>true</optional>
		</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>

spring-boot-starter-web dependency for building web applications using Spring MVC. It uses the tomcat as the default embedded container.




spring-boot-devtools dependency for automatic reloads or live reload of applications.

Create an entity class


Create Employee.java inside the in.bushansirgur.springboot.entity package and add the following content

package in.bushansirgur.springboot.entity;

public class Employee {
	
	private Integer id;
	
	private String name;
	
	private Integer age;
	
	private String location;

	public Employee(Integer id, String name, Integer age, String location) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		this.location = location;
	}

	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 Integer getAge() {
		return age;
	}

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

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", location=" + location + "]";
	}
}

Create Product.java inside in.bushansirgur.springboot.entity package and add the following content

package in.bushansirgur.springboot.entity;

public class Product {
	
	private Integer id;
	
	private String name;
	
	private Double price;

	public Product(Integer id, String name, Double price) {
		super();
		this.id = id;
		this.name = name;
		this.price = price;
	}

	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 Double getPrice() {
		return price;
	}

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

	@Override
	public String toString() {
		return "Product [id=" + id + ", name=" + name + ", price=" + price + "]";
	}
}

This is just a plain old java class that has setters, getters, constructors, and toString().

Create custom error response


Create ErrorObject.java inside in.bushansirgur.springboot.exceptions package and add the following content

package in.bushansirgur.springboot.exceptions;

public class ErrorObject {
	
	private int status;
	
	private String message;
	
	private long timestamp;

	public int getStatus() {
		return status;
	}

	public void setStatus(int status) {
		this.status = status;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public long getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}
	
	public ErrorObject() {
	}

	public ErrorObject(int status, String message, long timestamp) {
		this.status = status;
		this.message = message;
		this.timestamp = timestamp;
	}
	
	
}

This is just a plain old java class that contains fields that we want to show in the error response.

Create a custom exception


Create ResourceNotFoundException.java inside the in.bushansirgur.springboot.exceptions package and add the following content. This exception will handle the exceptions if the resource is not available.

package in.bushansirgur.springboot.exceptions;

public class ResourceNotFoundException extends RuntimeException{
	
	public ResourceNotFoundException(String message) {
		super(message);
	}
}

Create NoDataFoundException.java inside the in.bushansirgur.springboot.exceptions package and add the following content. This exception will handle the exceptions if the data is not available.

package in.bushansirgur.springboot.exceptions;

public class NoDataFoundException extends RuntimeException{
	
	public NoDataFoundException(String message) {
		super(message);
	}
}

Create a service class


Create EmployeeService.java inside in.bushansirgur.springboot.service package and add the following content

package in.bushansirgur.springboot.service;

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

import org.springframework.stereotype.Service;

import in.bushansirgur.springboot.entity.Employee;
import in.bushansirgur.springboot.exceptions.NoDataFoundException;
import in.bushansirgur.springboot.exceptions.ResourceNotFoundException;

@Service
public class EmployeeService {
	
	private static List<Employee> list = new ArrayList<>();
	
	static {
		list.add(new Employee(1, "Employee 1", 28, "India"));
		list.add(new Employee(2, "Employee 2", 29, "India"));
		list.add(new Employee(3, "Employee 3", 30, "India"));
		list.add(new Employee(4, "Employee 4", 43, "India"));
		list.add(new Employee(5, "Employee 5", 55, "India"));
	}
	
	public List<Employee> getList () {
		if (list.size() > 0) {
			return list;
		}
		throw new NoDataFoundException("No employees available");
	}
	
	public Employee getEmployee (Integer id) {
		Optional<Employee> theEmployee = list.stream().filter(e -> e.getId() == id).findFirst();
		if (!theEmployee.isPresent()) {
			throw new ResourceNotFoundException("Employee not found for the id->"+id);
		}
		return theEmployee.get();
	}
}

Create ProductService.java inside in.bushansirgur.springboot.service package and add the following content

package in.bushansirgur.springboot.service;

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

import org.springframework.stereotype.Service;

import in.bushansirgur.springboot.entity.Product;
import in.bushansirgur.springboot.exceptions.NoDataFoundException;
import in.bushansirgur.springboot.exceptions.ResourceNotFoundException;

@Service
public class ProductService {
	
	private static List<Product> list = new ArrayList<>();
	
	static {
		list.add(new Product(1, "iPhone XR", 500.00));
		list.add(new Product(2, "Galaxy Note 10", 700.00));
		list.add(new Product(3, "Oneplus Nord", 400.00));
		list.add(new Product(4, "Galaxy S10", 750.00));
		list.add(new Product(5, "iPhone 11", 700.00));
	}
	
	public List<Product> getList () {
		if (list.size() > 0) {
			return list;
		}
		throw new NoDataFoundException("No products available");
	}
	
	public Product getProduct(Integer id) {
		Optional<Product> theProduct = list.stream().filter(p -> p.getId() == id).findFirst();
		if (!theProduct.isPresent()) {
			throw new ResourceNotFoundException("Product not found for the id->"+id);
		}
		return theProduct.get();
	}
}

For the sake of this tutorial, we are loading some static data inside the static block.

Inside getList() method, we will check the size of the list, if the size is 0 then we will throw a custom exception NoDataFoundException.

Inside getProduct() or getEmployee() method, we will check the Employee or Product existence, if it is not present then we will throw a custom exception ResourceNotFoundException.

Create a global exception class


Create GlobalException.java inside in.bushansirgur.springboot.exceptions package and add the following content

package in.bushansirgur.springboot.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalException {
	
	@ExceptionHandler
	public ResponseEntity<ErrorObject> handleResourceNotFoundException (ResourceNotFoundException ex) {
		ErrorObject eObject = new ErrorObject();
		eObject.setStatusCode(HttpStatus.NOT_FOUND.value());
		eObject.setMessage(ex.getMessage());
		eObject.setTimestamp(System.currentTimeMillis());
		return new ResponseEntity<ErrorObject>(eObject, HttpStatus.NOT_FOUND);
	}
	
	@ExceptionHandler
	public ResponseEntity<ErrorObject> handleNoDataFoundException (NoDataFoundException ex) {
		ErrorObject eObject = new ErrorObject();
		eObject.setStatusCode(HttpStatus.NO_CONTENT.value());
		eObject.setMessage(ex.getMessage());
		eObject.setTimestamp(System.currentTimeMillis());
		return new ResponseEntity<ErrorObject>(eObject, HttpStatus.OK);
	}
	
	
}

We will add @ControllerAdvice annotation to this class to handle exceptions at the application level.




@ExceptionHandler annotation catches the exception for the specific exception.

Create Rest controller


Create EmployeeController.java inside the in.bushansirgur.springboot.controller package and add the following content

package in.bushansirgur.springboot.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import in.bushansirgur.springboot.entity.Employee;
import in.bushansirgur.springboot.service.EmployeeService;

@RestController
public class EmployeeController {
	
	@Autowired
	EmployeeService eService;
	
	@GetMapping("/employees")
	public List<Employee> getList () {
		return eService.getList();
	}
	
	@GetMapping("/employees/{id}")
	public Employee get (@PathVariable Integer id) {
		return eService.getEmployee(id);
	}  
}

Create ProductController.java inside the in.bushansirgur.springboot.controller package and add the following content

package in.bushansirgur.springboot.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import in.bushansirgur.springboot.entity.Product;
import in.bushansirgur.springboot.service.ProductService;

@RestController
public class ProductController {
	
	@Autowired
	ProductService pService;
	
	@GetMapping("/products")
	public List<Product> getList () {
		return pService.getList();
	}
	
	@GetMapping("/products/{id}")
	public Product get (@PathVariable Integer id) {
		return pService.getProduct(id);
	}  
}

Run the app


Run the application using the below maven command –

mvn spring-boot:run

Open the web browser and enter the following URL

  • http://localhost:8080/employees/55
{
    "statusCode": 404,
    "message": "Employee not found for the id->55",
    "timestamp": 1614856197537
}
  • http://localhost:8080/products/77
{
    "statusCode": 404,
    "message": "Product not found for the id->77",
    "timestamp": 1614856228040
}

Now to test the NoDataFoundException, remove all the static data from the service and enter the following URL

  • http://localhost:8080/employees
{
    "statusCode": 204,
    "message": "No employees available",
    "timestamp": 1614856303225
}
  • http://localhost:8080/products
{
    "statusCode": 204,
    "message": "No products available",
    "timestamp": 1614856359565
}




Bushan Sirgur

Hey guys, I am Bushan Sirgur from Banglore, India. Currently, I am working as an Associate project in an IT company.

This Post Has One Comment

  1. Soulman

    Great article sir. I learned something new!

Leave a Reply