Spring Boot Exception Handling

Introduction

In this chapter, we will learn about exception handling in Spring Boot 3.2 by building a Library Management System from scratch. We will cover how to set up the project, configure the H2 database, create entities and repositories, handle custom exceptions, and implement global exception handling using the @RestControllerAdvice annotation.

Table of Contents

  1. Introduction
  2. Create and Setup Spring Boot Project in IntelliJ IDEA
  3. Configure H2 Database
  4. Create Book Entity
  5. Create Book Repository
  6. Create Custom Exception – BookNotFoundException
  7. Create Service Layer
    • BookService
    • BookServiceImpl
  8. Create BookController
  9. Default Exception Handling in Spring Boot
  10. Create ErrorDetails
  11. Create GlobalExceptionHandler Class (handle specific and global exceptions)
  12. Test using Postman client
  13. Conclusion

Create and Setup Spring Boot Project in IntelliJ IDEA

Create a New Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: 3.2.0
    • Group: com.example
    • Artifact: library-management
    • Name: library-management
    • Description: Library Management System
    • Package name: com.example.librarymanagement
    • Packaging: Jar
    • Java: 17 (or the latest version available)
  3. Add Dependencies:

    • Spring Web
    • Spring Data JPA
    • H2 Database
  4. Generate the Project:

    • Click "Generate" to download the project as a ZIP file.
  5. Import Project into IntelliJ IDEA:

    • Open IntelliJ IDEA.
    • Click on "Open" and navigate to the downloaded ZIP file.
    • Extract the ZIP file and import the project.

Explanation

  • Spring Initializr: A web-based tool provided by Spring to bootstrap a new Spring Boot project with dependencies and configurations.
  • Group and Artifact: Define the project’s Maven coordinates.
  • Dependencies: Adding dependencies ensures that the necessary libraries are included in the project for web development, JPA, and H2 database connectivity.

Configure H2 Database

Update application.properties

  1. Open application.properties:

    • Navigate to src/main/resources/application.properties.
  2. Add H2 Database Configuration:

    • Add the following properties to configure the H2 database connection:
spring.datasource.url=jdbc:h2:mem:librarydb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true

Explanation

  • spring.datasource.url: The JDBC URL to connect to the H2 database in memory.
  • spring.datasource.driverClassName: The driver class name for H2 database.
  • spring.datasource.username: The username to connect to the H2 database.
  • spring.datasource.password: The password to connect to the H2 database.
  • spring.jpa.hibernate.ddl-auto: Specifies the Hibernate DDL mode. Setting it to update automatically updates the database schema based on the entity mappings.
  • spring.h2.console.enabled: Enables the H2 database console for easy access to the database through a web browser.

Create Book Entity

Create the Book Class

  1. Create a New Package:

    • In the src/main/java/com/example/librarymanagement directory, create a new package named model.
  2. Create the Book Class:

    • Inside the model package, create a new class named Book.
    • Add the following code to the Book class:
package com.example.librarymanagement.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String author;
    private String isbn;

    // Constructors
    public Book() {}

    public Book(String title, String author, String isbn) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
    }

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
}

Explanation

  • @Entity: Specifies that the class is an entity and is mapped to a database table.
  • @Id: Specifies the primary key of the entity.
  • @GeneratedValue: Specifies how the primary key should be generated. GenerationType.IDENTITY indicates that the primary key is auto-incremented.

Create Book Repository

Create the BookRepository Interface

  1. Create a New Package:

    • In the src/main/java/com/example/librarymanagement directory, create a new package named repository.
  2. Create the BookRepository Interface:

    • Inside the repository package, create a new interface named BookRepository.
    • Add the following code to the BookRepository interface:
package com.example.librarymanagement.repository;

import com.example.librarymanagement.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {
}

Explanation

  • JpaRepository: The BookRepository interface extends JpaRepository, providing CRUD operations for the Book entity. The JpaRepository interface includes methods like save(), findById(), findAll(), deleteById(), etc.
  • Generics: The JpaRepository interface takes two parameters: the entity type (Book) and the type of its primary key (Long).

Create Custom Exception – BookNotFoundException

Create the BookNotFoundException Class

  1. Create a New Package:

    • In the src/main/java/com/example/librarymanagement directory, create a new package named exception.
  2. Create the BookNotFoundException Class:

    • Inside the exception package, create a new class named BookNotFoundException.
    • Add the following code to the BookNotFoundException class:
package com.example.librarymanagement.exception;

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

Explanation

  • Custom Exception: BookNotFoundException extends RuntimeException to create a custom exception. It includes a constructor that takes a message parameter, which is passed to the superclass constructor.

Create Service Layer

Create BookService Interface

  1. Create a New Package:

    • In the src/main/java/com/example/librarymanagement directory, create a new package named service.
  2. Create the BookService Interface:

    • Inside the service package, create a new interface named BookService.
    • Add the following code to the BookService interface:
package com.example.librarymanagement.service;

import com.example.librarymanagement.model.Book;
import java.util.List;

public interface BookService {
    Book saveBook(Book book);
    Book getBookById(Long id);
    List<Book> getAllBooks();
    Book updateBook(Long id, Book bookDetails);
    void deleteBook(Long id);
}

Explanation

  • Service Interface: Defines the contract for the service layer. It includes methods for saving, retrieving, updating, and deleting books.

Create BookServiceImpl Class

  1. Create the BookServiceImpl Class:
    • Inside the service package, create a new class named BookServiceImpl.
    • Add the following code to the BookServiceImpl class:
package com.example.librarymanagement.service;

import com.example.librarymanagement.exception.BookNotFoundException;
import com.example.librarymanagement.model.Book;
import com.example.librarymanagement.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookRepository bookRepository;

    @Override
    public Book saveBook(Book book) {
        return bookRepository.save(book);
    }

    @Override
    public Book getBookById(Long id) {
        return bookRepository.findById(id)
                .orElseThrow(() -> new BookNotFoundException("Book not found with id: " + id));
    }

    @Override
    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }

    @Override
    public Book updateBook(Long id, Book bookDetails) {
        Book book = bookRepository.findById(id)
                .orElseThrow(() -> new BookNot

FoundException("Book not found with id: " + id));

        book.setTitle(bookDetails.getTitle());
        book.setAuthor(bookDetails.getAuthor());
        book.setIsbn(bookDetails.getIsbn());

        return bookRepository.save(book);
    }

    @Override
    public void deleteBook(Long id) {
        Book book = bookRepository.findById(id)
                .orElseThrow(() -> new BookNotFoundException("Book not found with id: " + id));

        bookRepository.delete(book);
    }
}

Explanation

  • @Service: Indicates that this class is a service component in the Spring context.
  • BookRepository: The BookRepository instance is injected into the service class to interact with the database.
  • Exception Handling: The getBookById, updateBook, and deleteBook methods throw a BookNotFoundException if the book is not found.

Create BookController

Create the BookController Class

  1. Create a New Package:

    • In the src/main/java/com/example/librarymanagement directory, create a new package named controller.
  2. Create the BookController Class:

    • Inside the controller package, create a new class named BookController.
    • Add the following code to the BookController class:
package com.example.librarymanagement.controller;

import com.example.librarymanagement.model.Book;
import com.example.librarymanagement.service.BookService;
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;

@RestController
@RequestMapping("/api/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @PostMapping
    public ResponseEntity<Book> saveBook(@RequestBody Book book) {
        Book savedBook = bookService.saveBook(book);
        return new ResponseEntity<>(savedBook, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Book> getBookById(@PathVariable Long id) {
        Book book = bookService.getBookById(id);
        return new ResponseEntity<>(book, HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity<List<Book>> getAllBooks() {
        List<Book> books = bookService.getAllBooks();
        return new ResponseEntity<>(books, HttpStatus.OK);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Book> updateBook(@PathVariable Long id, @RequestBody Book bookDetails) {
        Book updatedBook = bookService.updateBook(id, bookDetails);
        return new ResponseEntity<>(updatedBook, HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteBook(@PathVariable Long id) {
        bookService.deleteBook(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

Explanation

  • @RestController: Indicates that this class is a REST controller.
  • @RequestMapping("/api/books"): Maps HTTP requests to /api/books to methods in this controller.
  • @PostMapping: Handles HTTP POST requests to save a book.
  • @GetMapping("/{id}"): Handles HTTP GET requests to retrieve a book by ID.
  • @GetMapping: Handles HTTP GET requests to retrieve all books.
  • @PutMapping("/{id}"): Handles HTTP PUT requests to update a book’s details.
  • @DeleteMapping("/{id}"): Handles HTTP DELETE requests to delete a book by ID.

Default Exception Handling in Spring Boot

Spring Boot provides a default exception handling mechanism using the @ExceptionHandler annotation and the @ControllerAdvice or @RestControllerAdvice annotations. However, you can customize the exception handling to meet your application’s requirements.

Create ErrorDetails

Create the ErrorDetails Class

  1. Create a New Package:

    • In the src/main/java/com/example/librarymanagement directory, create a new package named dto.
  2. Create the ErrorDetails Class:

    • Inside the dto package, create a new class named ErrorDetails.
    • Add the following code to the ErrorDetails class:
package com.example.librarymanagement.dto;

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;
    }

    // Getters and Setters
    public Date getTimestamp() {
        return timestamp;
    }

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

    public String getMessage() {
        return message;
    }

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

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }
}

Explanation

  • ErrorDetails: A simple DTO (Data Transfer Object) class to encapsulate error details such as the timestamp, message, and additional details.

Create GlobalExceptionHandler Class

Create the GlobalExceptionHandler Class

  1. Create a New Package:

    • In the src/main/java/com/example/librarymanagement directory, create a new package named exceptionhandler.
  2. Create the GlobalExceptionHandler Class:

    • Inside the exceptionhandler package, create a new class named GlobalExceptionHandler.
    • Add the following code to the GlobalExceptionHandler class:
package com.example.librarymanagement.exceptionhandler;

import com.example.librarymanagement.dto.ErrorDetails;
import com.example.librarymanagement.exception.BookNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Date;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<ErrorDetails> handleBookNotFoundException(BookNotFoundException ex) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), "Book Not Found");
        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorDetails> handleGlobalException(Exception ex) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), "Internal Server Error");
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Explanation

  • @RestControllerAdvice: Indicates that this class provides global exception handling for REST controllers.
  • @ExceptionHandler(BookNotFoundException.class): Handles BookNotFoundException and returns a ResponseEntity with error details and HTTP status 404 Not Found.
  • @ExceptionHandler(Exception.class): Handles all other exceptions and returns a ResponseEntity with error details and HTTP status 500 Internal Server Error.

Test Using Postman Client

Step-by-Step Testing

  1. Open Postman:

    • Launch the Postman client from your installed applications.
  2. Create a New Request:

    • Click on "New" and then select "Request".
  3. Set Request Type and URL:

    • Set the request type to GET.
    • Enter the URL: http://localhost:8080/api/books/1 (assuming there is no book with ID 1).
  4. Send Request:

    • Click the "Send" button.
    • Verify that the response status is 404 Not Found and the response body contains the error details.

Explanation

  • Request Type: GET indicates that we are retrieving data.
  • URL: Points to the Get Book by ID REST API endpoint with an ID that does not exist.
  • Response: Verifies that the custom exception handling mechanism is working correctly by returning the appropriate error details and HTTP status.

Conclusion

In this chapter, we built a Library Management System from scratch using Spring Boot 3.2. We configured the H2 database, created entities and repositories, handled custom exceptions, and implemented global exception handling using the @RestControllerAdvice annotation. We tested the exception handling mechanism using the Postman client. Each step was explained in detail to help you understand the process.

Leave a Comment

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

Scroll to Top