Global Exception Handler in Spring Boot
Tags: webdev, springboot, java, tutorial
Published: 2023-10-11 03:23:00

In this tutorial, we're gonna look at an Spring Boot example that uses <code>@ControllerAdvice</code> and <code>@ExceptionHandler</code> for global exception handling in Spring Boot.

Related Posts:
- <a href="https://bezkoder.com/spring-boot-restcontrolleradvice/">@RestControllerAdvice example in Spring Boot</a>
- <a href="https://bezkoder.com/spring-boot-jpa-crud-rest-api/">Spring Boot, Spring Data JPA – Rest CRUD API example</a>
- <a href="https://bezkoder.com/spring-boot-pagination-filter-jpa-pageable/">Spring Boot Pagination & Filter example</a>
- <a href="https://bezkoder.com/spring-data-sort-multiple-columns/">Spring Boot Sort/Order by multiple Columns</a>
- Documentation: <a href="https://www.bezkoder.com/spring-boot-swagger-3/">Spring Boot Swagger 3 example</a>
- Caching: <a href="https://www.bezkoder.com/spring-boot-redis-cache-example/">Spring Boot Redis Cache example</a>
- Validation: <a href="https://www.bezkoder.com/spring-boot-validate-request-body/">Validate Request Body in Spring Boot</a>

More Practice:
- <a href="https://bezkoder.com/spring-boot-file-upload/">Spring Boot Multipart File upload example</a>
- <a href="https://bezkoder.com/spring-boot-jwt-authentication/">Spring Boot Token based Authentication with Spring Security & JWT</a>

Unit Test:
- <a href="https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/">Spring Boot Unit Test for JPA Repository</a>
- <a href="https://www.bezkoder.com/spring-boot-webmvctest/">Spring Boot Unit Test for Rest Controller</a>

<h2>Exception handling Problem</h2>
We've created Rest Controller for CRUD Operations and finder method.
Let look at the code:
(step by step to build the Rest APIs is in:
- <a href="https://bezkoder.com/spring-boot-jpa-h2-example/">Spring Boot Data JPA + H2 CRUD example</a>
- <a href="https://bezkoder.com/spring-boot-jpa-crud-rest-api/">Spring Boot Data JPA + MySQL CRUD example</a>
- <a href="https://bezkoder.com/spring-boot-postgresql-example/">Spring Boot Data JPA + PostgreSQL CRUD example</a>
- <a href="https://www.bezkoder.com/spring-boot-sql-server/">Spring Boot Data JPA + SQL Server</a>
- <a href="https://www.bezkoder.com/spring-boot-hibernate-oracle/">Spring Boot Data JPA + Oracle example</a>
- <a href="https://bezkoder.com/spring-boot-mongodb-crud/">Spring Boot MongoDB CRUD example</a>
- <a href="https://bezkoder.com/spring-boot-cassandra-crud/">Spring Boot Cassandra CRUD example</a>)

```java
@RestController
public class TutorialController {

  @Autowired
  TutorialRepository tutorialRepository;

  @GetMapping("/tutorials")
  public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
    try {
      ...
      return new ResponseEntity<>(tutorials, HttpStatus.OK);
    } catch (Exception e) {
      return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
  
  @GetMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
    Optional<Tutorial> tutorialData = tutorialRepository.findById(id);

    if (tutorialData.isPresent()) {
      return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }
  
  @PutMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
    Optional<Tutorial> tutorialData = tutorialRepository.findById(id);

    if (tutorialData.isPresent()) {
      ...
      return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }

  ...

  @DeleteMapping("/tutorials/{id}")
  public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
    try {
      tutorialRepository.deleteById(id);
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  @DeleteMapping("/tutorials")
  public ResponseEntity<HttpStatus> deleteAllTutorials() {
    // try and catch
  }

  @GetMapping("/tutorials/published")
  public ResponseEntity<List<Tutorial>> findByPublished() {
    // try and catch
  }
}
```

You can see that we use try/catch many times for similar exception (<strong>INTERNAL_SERVER_ERROR</strong>), and there are also many cases that return <strong>NOT_FOUND</strong>.

Is there any way to keep them simple, any way to attach the error response message smartly and flexibility?
Let's solve the problem now.

<h2>Exception Handler with Controller Advice in Spring</h2>
Spring supports exception handling by a global Exception Handler (<a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html">@ExceptionHandler</a>) with Controller Advice (<a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html">@ControllerAdvice</a>). This enables a mechanism that makes <code>ResponseEntity</code> work with the type safety and flexibility of <code>@ExceptionHandler</code>:

```java
@ControllerAdvice
public class ControllerExceptionHandler {
  
  @ExceptionHandler(value = {ResourceNotFoundException.class, CertainException.class})
  public ResponseEntity<ErrorMessage> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
    ErrorMessage message = new ErrorMessage(
        status,
        date,
        ex.getMessage(),
        description);
    
    return new ResponseEntity<ErrorMessage>(message, HttpStatus.NOT_FOUND);
  }
}
```

The <code>@ControllerAdvice</code> annotation is specialization of <code>@Component</code> annotation so that it is auto-detected via classpath scanning. A Controller Advice is a kind of interceptor that surrounds the logic in our Controllers and allows us to apply some common logic to them. 

Its methods (annotated with <code>@ExceptionHandler</code>) are shared globally across multiple <code>@Controller</code> components to capture exceptions and translate them to HTTP responses. The <code>@ExceptionHandler</code> annotation indicates which type of Exception we want to handle. The <code>exception</code> instance and the <code>request</code> will be injected via method arguments.

By using two annotations together, we can:
<ul>
	<li>control the body of the response along with status code</li>
	<li>handle several exceptions in the same method</li>
</ul>

<h2>@ResponseStatus</h2>
In the example above, we use <code>@ControllerAdvice</code> for REST web services and return <code>ResponseEntity</code> object additionally.

Spring also provides <code>@ResponseBody</code> annotation which tells a controller that the object returned is automatically serialized into JSON and passed it to the <code>HttpResponse</code> object. This way does not require <code>ResponseEntity</code> but you need to use <code>@ResponseStatus</code> to set the HTTP status code for that exception.

```java
@ControllerAdvice
@ResponseBody
public class ControllerExceptionHandler {

  @ExceptionHandler(ResourceNotFoundException.class)
  @ResponseStatus(value = HttpStatus.NOT_FOUND)
  public ErrorMessage resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
    ErrorMessage message = new ErrorMessage(...);
    return message;
  }
}
```

<h2>Setup Spring Boot Project</h2>
You can follow step by step, or get source code in one of following posts:
- <a href="https://bezkoder.com/spring-boot-jpa-h2-example/">Spring Boot Data JPA + H2 CRUD example</a>
- <a href="https://bezkoder.com/spring-boot-jpa-crud-rest-api/">Spring Boot Data JPA + MySQL CRUD example</a>
- <a href="https://bezkoder.com/spring-boot-postgresql-example/">Spring Boot Data JPA + PostgreSQL CRUD example</a>
- <a href="https://www.bezkoder.com/spring-boot-sql-server/">Spring Boot Data JPA + SQL Server</a>
- <a href="https://www.bezkoder.com/spring-boot-hibernate-oracle/">Spring Boot Data JPA + Oracle example</a>
- <a href="https://bezkoder.com/spring-boot-mongodb-crud/">Spring Boot MongoDB CRUD example</a>
- <a href="https://bezkoder.com/spring-boot-cassandra-crud/">Spring Boot Cassandra CRUD example</a>

The Spring Project contains structure that we only need to add some changes to make the pagination work well.

<img src="https://bezkoder.com/wp-content/uploads/2019/12/spring-boot-data-jpa-crud-example-project-structure.png" alt="spring-boot-data-jpa-crud-example-project-structure" width="314" height="391" class="alignnone size-full wp-image-1537" />

Or you can get the new Github source code at the end of this tutorial.

The final project structure will be like this:

<img src="https://bezkoder.com/wp-content/uploads/2019/10/spring-boot-controlleradvice-exceptionhandler-example-project-structure.png" alt="spring-boot-controlleradvice-exceptionhandler-example-project-structure" width="340" height="452" class="alignnone size-full wp-image-4155" />

<h2>Define Error Response Message</h2>
We want to create a our own message response structure instead of using default error response provided by Spring Boot.
Let's define a specific error response structure.

<strong>exception</strong>/<em>ErrorMessage.java</em>
```java
package com.bezkoder.spring.exhandling.exception;

import java.util.Date;

public class ErrorMessage {
  private int statusCode;
  private Date timestamp;
  private String message;
  private String description;

  public ErrorMessage(int statusCode, Date timestamp, String message, String description) {
    this.statusCode = statusCode;
    this.timestamp = timestamp;
    this.message = message;
    this.description = description;
  }

  public int getStatusCode() {
    return statusCode;
  }

  public Date getTimestamp() {
    return timestamp;
  }

  public String getMessage() {
    return message;
  }

  public String getDescription() {
    return description;
  }
}
```

<h2>Create Custom Exception</h2>
We're gonna throw an exception for Resource not found in Spring Boot controller.
Lets create a <code>ResourceNotFoundException</code> class that extends <code>RuntimeException</code>.

<strong>exception</strong>/<em>ResourceNotFoundException.java</em>
```java
package com.bezkoder.spring.exhandling.exception;

public class ResourceNotFoundException extends RuntimeException {

  private static final long serialVersionUID = 1L;

  public ResourceNotFoundException(String msg) {
    super(msg);
  }
}
```

<h2>Create Controller Advice with @ExceptionHandler</h2>
Now we're gonna create a special class which is annotated by <code>@ControllerAdvice</code> annotation. This class handles specific exception (<code>ResoureNotFoundException</code>) and global Exception in only one place.

<strong>exception</strong>/<em>ControllerExceptionHandler.java</em>
```java
package com.bezkoder.spring.exhandling.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;

import com.bezkoder.spring.exhandling.exception.ErrorMessage;
import com.bezkoder.spring.exhandling.exception.ResourceNotFoundException;

@ControllerAdvice
public class ControllerExceptionHandler {

  @ExceptionHandler(ResourceNotFoundException.class)
  public ResponseEntity<ErrorMessage> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
    ErrorMessage message = new ErrorMessage(
        HttpStatus.NOT_FOUND.value(),
        new Date(),
        ex.getMessage(),
        request.getDescription(false));
    
    return new ResponseEntity<ErrorMessage>(message, HttpStatus.NOT_FOUND);
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorMessage> globalExceptionHandler(Exception ex, WebRequest request) {
    ErrorMessage message = new ErrorMessage(
        HttpStatus.INTERNAL_SERVER_ERROR.value(),
        new Date(),
        ex.getMessage(),
        request.getDescription(false));
    
    return new ResponseEntity<ErrorMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}
```

<h2>Modify Controller for using @ControllerAdvice</h2>
Our Rest Controller now doesn't have try/catch block, and it will throw <code>ResourceNotFoundException</code> where we want to send NOT_FOUND notification in response message.

<strong>controller</strong>/<em>TutorialController.java</em>
```java
package com.bezkoder.spring.exhandling.controller;

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

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

import com.bezkoder.spring.exhandling.exception.ResourceNotFoundException;
import com.bezkoder.spring.exhandling.model.Tutorial;
import com.bezkoder.spring.exhandling.repository.TutorialRepository;

@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TutorialController {

  @Autowired
  TutorialRepository tutorialRepository;

  @GetMapping("/tutorials")
  public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
    List<Tutorial> tutorials = new ArrayList<Tutorial>();

    if (title == null)
      tutorialRepository.findAll().forEach(tutorials::add);
    else
      tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);

    if (tutorials.isEmpty()) {
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

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

  @GetMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
    Tutorial tutorial = tutorialRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));

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

  @PostMapping("/tutorials")
  public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
    Tutorial _tutorial = tutorialRepository.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false));
    return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
  }

  @PutMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
    Tutorial _tutorial = tutorialRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));

    _tutorial.setTitle(tutorial.getTitle());
    _tutorial.setDescription(tutorial.getDescription());
    _tutorial.setPublished(tutorial.isPublished());
    
    return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
  }

  @DeleteMapping("/tutorials/{id}")
  public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
    tutorialRepository.deleteById(id);
    
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
  }

  @DeleteMapping("/tutorials")
  public ResponseEntity<HttpStatus> deleteAllTutorials() {
    tutorialRepository.deleteAll();
    
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
  }

  @GetMapping("/tutorials/published")
  public ResponseEntity<List<Tutorial>> findByPublished() {
    List<Tutorial> tutorials = tutorialRepository.findByPublished(true);

    if (tutorials.isEmpty()) {
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
    
    return new ResponseEntity<>(tutorials, HttpStatus.OK);
  }

}
```

<h2>Run and Test</h2>
We finish implementing CRUD REST APIs and exception handling for it.
Run Spring Boot application with command: <code>mvn spring-boot:run</code>.

- Get a non-existent tutorial:

<img src="https://bezkoder.com/wp-content/uploads/2020/08/spring-boot-controlleradvice-exceptionhandler-example-demo-get-404.png" alt="spring-boot-controlleradvice-exceptionhandler-example-demo-get-404" width="620" height="420" class="alignnone size-full wp-image-4157" />

- Update a non-existent tutorial:

<img src="https://bezkoder.com/wp-content/uploads/2020/08/spring-boot-controlleradvice-exceptionhandler-example-demo-update-404.png" alt="spring-boot-controlleradvice-exceptionhandler-example-demo-update-404" width="620" height="440" class="alignnone size-full wp-image-4159" />

- Create tutorial with wrong fields:

<img src="https://bezkoder.com/wp-content/uploads/2020/08/spring-boot-controlleradvice-exceptionhandler-example-demo-create-500.png" alt="spring-boot-controlleradvice-exceptionhandler-example-demo-create-500" width="650" height="450" class="alignnone size-full wp-image-4160" />

- Delete a non-existent tutorial:

<img src="https://bezkoder.com/wp-content/uploads/2020/08/spring-boot-controlleradvice-exceptionhandler-example-demo-delete-500.png" alt="spring-boot-controlleradvice-exceptionhandler-example-demo-delete-500" width="740" height="382" class="alignnone size-full wp-image-4158" />

<h2>Conclusion</h2>
Today we've built Global Exception Handling class for Spring Boot Rest APIs using <code>@ControllerAdvice</code> and <code>@ExceptionHandler</code>. Now you can create your own custom exception handler class or handle global exception in single place at ease.

If you want to add Pagination to this Spring project, you can find the instruction at:
<a href="https://bezkoder.com/spring-boot-pagination-filter-jpa-pageable/">Spring Boot Pagination & Filter example | Spring JPA, Pageable</a>

To sort/order by multiple fields:
<a href="https://bezkoder.com/spring-data-sort-multiple-columns/">Spring Data JPA Sort/Order by multiple Columns | Spring Boot</a>

Or way to write Unit Test:
- <a href="https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/">Spring Boot Unit Test for JPA Repository</a>
- <a href="https://www.bezkoder.com/spring-boot-webmvctest/">Spring Boot Unit Test for Rest Controller</a>

You can also know how to deploy this Spring Boot App on AWS (for free) with <a href="https://bezkoder.com/deploy-spring-boot-aws-eb/">this tutorial</a>.
Dockerize with <a href="https://www.bezkoder.com/docker-compose-spring-boot-mysql/">Docker Compose: Spring Boot and MySQL example</a>
Or: <a href="https://www.bezkoder.com/docker-compose-spring-boot-postgres/">Docker Compose: Spring Boot and Postgres example</a>

Happy learning! See you again.

<h2>Further Reading</h2>
<ul>
	<li><a href="https://bezkoder.com/spring-boot-jwt-authentication/">Secure Spring Boot App with Spring Security & JWT Authentication</a></li>
	<li><a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference">Spring Data JPA Reference Documentation</a></li>
</ul>

<h2>Source Code</h2>
You can find the complete source code for this tutorial on <a href="https://github.com/bezkoder/spring-boot-exception-handling">Github</a>.

You can also use <a href="https://bezkoder.com/spring-boot-restcontrolleradvice/">@RestControllerAdvice in Spring Boot</a>

Documentation: <a href="https://www.bezkoder.com/spring-boot-swagger-3/">Spring Boot + Swagger 3 example (with OpenAPI 3)</a>
Caching: <a href="https://www.bezkoder.com/spring-boot-redis-cache-example/">Spring Boot Redis Cache example</a>
Validation: <a href="https://www.bezkoder.com/spring-boot-validate-request-body/">Validate Request Body in Spring Boot</a>