Back to: Spring Boot Tutorial: Building RESTful APIs from Scratch
Introduction
In this tutorial, we will see how to do Exception Handling in Spring Boot REST API that we build in the previous modules.
Exception handling is an important aspect of any application, including Spring Boot applications. When an unexpected error or exception occurs in a Spring Boot application, it can cause the application to crash or behave unexpectedly. Therefore, it is important to handle exceptions properly to provide a better user experience and ensure the stability of the application.
In Spring Boot, you can handle exceptions by using the @ExceptionHandler
annotation. This annotation is used to define a method that can handle a specific exception type. When an exception occurs in the application, Spring Boot will look for a matching @ExceptionHandler
method and execute it.
Handling Exception in Spring Boot Application
Here are the steps to handle exceptions in our Spring Boot Bookstore REST API:
Create custom exception classes
Create a custom exception class to represent the error scenario. For example, we can create a BookNotFoundException
class to handle the case when a book is not found.
package com.notearena.bookstore.exception;
/*
* ------------------------------------------------------|
* Spring Boot REST API
* =====================================
* 2023 NoteArena.com opensource project
* @Author: Mamun Kayum
* @Modified:
* @Version: 1.0
* Feel free use this implementation
*-------------------------------------------------------|
*/
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException() {
super();
}
public BookNotFoundException(String message) {
super(message);
}
public BookNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
Next, we can create a BookAlreadyExistsException class to handle the case when a book already exists.
package com.notearena.bookstore.exception;
/*
* ------------------------------------------------------|
* Spring boot REST API
* =====================================
* 2023 NoteArena.com opensource project
* @Author: Mamun Kayum
* @Modified:
* @Version: 1.0
* Feel free use this implementation
*-------------------------------------------------------|
*/
public class BookAlreadyExistsException extends RuntimeException{
public BookAlreadyExistsException() {
super();
}
public BookAlreadyExistsException(String message) {
super(message);
}
}
Create a new class called ErrorResponse
Create a new class called ErrorResponse
under the com.notearena.bookstore.exception
package. This class will be used to send an error response back to the client.
package com.notearena.bookstore.exception;
import org.springframework.http.HttpStatus;
/*
* ------------------------------------------------------|
* Spring Boot REST API
* =====================================
* 2023 NoteArena.com opensource project
* @Author: Mamun Kayum
* @Modified:
* @Version: 1.0
* Feel free use this implementation
*-------------------------------------------------------|
*/
public class ErrorResponse {
private HttpStatus status;
private String message;
private long timestamp;
public ErrorResponse(HttpStatus status, String message, long timestamp) {
this.status = status;
this.message = message;
this.timestamp = timestamp;
}
// getters and
public HttpStatus getStatus() {
return status;
}
public void setStatus(HttpStatus 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;
}
}
Create a custom exception handler class
Next, we need to add the custom exception handler to the application. We can do this by creating a class that implements the ResponseEntityExceptionHandler
class and annotating it with @ControllerAdvice
. This will ensure that the exception handling applies to all controllers in the application. Here is the example implementation:
package com.notearena.bookstore.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.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
/*
* ------------------------------------------------------|
* Spring boot REST API
* =====================================
* 2023 NoteArena.com opensource project
* @Author: Mamun Kayum
* @Modified:
* @Version: 1.0
* Feel free use this implementation
*-------------------------------------------------------|
*/
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage(), System.currentTimeMillis());
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<Object> handleBookNotFoundException(BookNotFoundException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage(), System.currentTimeMillis());
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler({ BookAlreadyExistsException.class })
protected ResponseEntity<Object> handleBookAlreadyExists(
Exception ex, WebRequest request) {
String message = "Book already exists";
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.CONFLICT, ex.getMessage(), System.currentTimeMillis());
return new ResponseEntity<>(errorResponse, HttpStatus.CONFLICT);
}
@ExceptionHandler(InvalidRequestException.class)
public ResponseEntity<Object> handleInvalidRequestException(InvalidRequestException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), System.currentTimeMillis());
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
In this class, we define the exception handlers for each exception we want to handle. In this example, we have defined handlers for BookNotFoundException
, BookAlreadyExistsException, InvalidRequestException and Exception
.
The first method, handleBookNotFoundException
is annotated with @ExceptionHandler(BookNotFoundException.class)
to indicate that it will handle BookNotFoundException
. Inside the method, we create a new ErrorResponse
object with the appropriate status code, error message, and timestamp. We then return a new ResponseEntity
object with the error response and HttpStatus.NOT_FOUND
.
handleBookAlreadyExists
method handles the BookAlreadyExistsException
and returns a 409 Conflict
status code with a custom error message and timestamp.
handleAllExceptions
method handles all other exceptions and returns a 500 Internal Server Error
status code with the exception message and timestamp.
Finally, we define a handleInvalidRequestException
method that is to return an exception message whenever we will have an invalid request.
In the controller class, we can throw the BookNotFoundException
and other exceptions whenever a book is not found, a book already exists, and many more.
Updated BookController
class with Exception Handling
We will modify the BookController
class to throw a ResourceNotFoundException
when the requested book resource is not found. Likewise, we will throw other exceptions based on the functions of the methods.
package com.notearena.bookstore.controller;
import com.notearena.bookstore.exception.BookAlreadyExistsException;
import com.notearena.bookstore.exception.BookNotFoundException;
import com.notearena.bookstore.model.Book;
import com.notearena.bookstore.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
/*
* ------------------------------------------------------|
* Spring Boot REST API
* =====================================
* 2023 NoteArena.com opensource project
* @Author: Mamun Kayum
* @Modified:
* @Version: 1.0
* Feel free use this implementation
*-------------------------------------------------------|
*/
@RestController
@RequestMapping("/books")
public class BookController {
private final BookRepository bookRepository;
@Autowired
public BookController(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@GetMapping
public ResponseEntity<List<Book>> getAllBooks() {
List<Book> books = bookRepository.findAll();
return ResponseEntity.ok(books);
}
@GetMapping("/{id}")
public Book getBookById(@PathVariable Long id) {
Optional<Book> book = bookRepository.findById(id);
return book.orElseThrow(() -> new BookNotFoundException("Book not found with id: " + id));
}
@PostMapping
public ResponseEntity<Void> createBook(@RequestBody Book book) {
bookRepository.findByTitleAndAuthor(book).ifPresent(book1 -> {throw new BookAlreadyExistsException("Book already exists!");});
bookRepository.save(book);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@PutMapping("/{id}")
public ResponseEntity<Book> updateBook(@PathVariable("id") Long id, @RequestBody Book book) {
Book existingBook = bookRepository.findById(id)
.orElseThrow(() -> new BookNotFoundException("Book not found with id " + id));
existingBook.setTitle(book.getTitle());
existingBook.setAuthor(book.getAuthor());
Book updatedBook = bookRepository.update(existingBook.getId(),existingBook);
return new ResponseEntity<>(updatedBook, HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteBook(@PathVariable("id") Long id) {
Book book = bookRepository.findById(id)
.orElseThrow(() -> new BookNotFoundException("Book not found with id " + id));
bookRepository.delete(book.getId());
return ResponseEntity.noContent().build();
}
}
When getBookById
method is called with an invalid book ID, a ResourceNotFoundException
will be thrown, and the global exception handler will be invoked to return an appropriate error response.
Here, we call the orElseThrow
method on the Optional<Book>
returned by findById
. This method takes a Supplier
argument that returns an exception to be thrown if the optional is empty. In this case, we create and throw a new BookNotFoundException
with the given id.
Testing the API
Now that we have our data custom exception class, exception handler, and controller defined, let’s test our API using Postman.
1. Open Postman and create a new request.
2. Create a new request and set the request method to POST and the request URL to http://localhost:8080/books.
In the request body, enter JSON data for a new book. For example:
{ "title": "Building a REST API", "author": "Mamun Kayum" }

Now if we send the same JSON data for the book, it will return us response with an exception message:

Create a new request and set the request method to GET and the request URL to http://localhost:8080/books/{id}
, where {id} is the id of a book in our data store. Use an id that is not available in our data store
.
Click the Send button to make the request. You should see a response with an error message that the book is not found.

Now, if we want to delete a book that is not available, we will see an error response:

Conclusion
By using the @ExceptionHandler
annotation to handle exceptions in our Spring Boot REST API, we can ensure that our API returns meaningful error messages and status codes to clients, improving the overall user experience.
Visit our springboot-projects GitHub repository for the code.
Also, see the example code JavaExamples_NoteArena in our GitHub repository. See complete examples in our GitHub repositories.
Follow us on social media
Follow Author