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
- Introduction
- Create and Setup Spring Boot Project in IntelliJ IDEA
- Configure H2 Database
- Create Book Entity
- Create Book Repository
- Create Custom Exception – BookNotFoundException
- Create Service Layer
- BookService
- BookServiceImpl
- Create BookController
- Default Exception Handling in Spring Boot
- Create ErrorDetails
- Create GlobalExceptionHandler Class (handle specific and global exceptions)
- Test using Postman client
- Conclusion
Create and Setup Spring Boot Project in IntelliJ IDEA
Create a New Spring Boot Project
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
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)
-
Add Dependencies:
- Spring Web
- Spring Data JPA
- H2 Database
-
Generate the Project:
- Click "Generate" to download the project as a ZIP file.
-
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
-
Open
application.properties:- Navigate to
src/main/resources/application.properties.
- Navigate to
-
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 toupdateautomatically 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
-
Create a New Package:
- In the
src/main/java/com/example/librarymanagementdirectory, create a new package namedmodel.
- In the
-
Create the
BookClass:- Inside the
modelpackage, create a new class namedBook. - Add the following code to the
Bookclass:
- Inside the
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.IDENTITYindicates that the primary key is auto-incremented.
Create Book Repository
Create the BookRepository Interface
-
Create a New Package:
- In the
src/main/java/com/example/librarymanagementdirectory, create a new package namedrepository.
- In the
-
Create the
BookRepositoryInterface:- Inside the
repositorypackage, create a new interface namedBookRepository. - Add the following code to the
BookRepositoryinterface:
- Inside the
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: TheBookRepositoryinterface extendsJpaRepository, providing CRUD operations for theBookentity. TheJpaRepositoryinterface includes methods likesave(),findById(),findAll(),deleteById(), etc.- Generics: The
JpaRepositoryinterface takes two parameters: the entity type (Book) and the type of its primary key (Long).
Create Custom Exception – BookNotFoundException
Create the BookNotFoundException Class
-
Create a New Package:
- In the
src/main/java/com/example/librarymanagementdirectory, create a new package namedexception.
- In the
-
Create the
BookNotFoundExceptionClass:- Inside the
exceptionpackage, create a new class namedBookNotFoundException. - Add the following code to the
BookNotFoundExceptionclass:
- Inside the
package com.example.librarymanagement.exception;
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(String message) {
super(message);
}
}
Explanation
- Custom Exception:
BookNotFoundExceptionextendsRuntimeExceptionto 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
-
Create a New Package:
- In the
src/main/java/com/example/librarymanagementdirectory, create a new package namedservice.
- In the
-
Create the
BookServiceInterface:- Inside the
servicepackage, create a new interface namedBookService. - Add the following code to the
BookServiceinterface:
- Inside the
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
- Create the
BookServiceImplClass:- Inside the
servicepackage, create a new class namedBookServiceImpl. - Add the following code to the
BookServiceImplclass:
- Inside the
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: TheBookRepositoryinstance is injected into the service class to interact with the database.- Exception Handling: The
getBookById,updateBook, anddeleteBookmethods throw aBookNotFoundExceptionif the book is not found.
Create BookController
Create the BookController Class
-
Create a New Package:
- In the
src/main/java/com/example/librarymanagementdirectory, create a new package namedcontroller.
- In the
-
Create the
BookControllerClass:- Inside the
controllerpackage, create a new class namedBookController. - Add the following code to the
BookControllerclass:
- Inside the
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/booksto 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
-
Create a New Package:
- In the
src/main/java/com/example/librarymanagementdirectory, create a new package nameddto.
- In the
-
Create the
ErrorDetailsClass:- Inside the
dtopackage, create a new class namedErrorDetails. - Add the following code to the
ErrorDetailsclass:
- Inside the
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
-
Create a New Package:
- In the
src/main/java/com/example/librarymanagementdirectory, create a new package namedexceptionhandler.
- In the
-
Create the
GlobalExceptionHandlerClass:- Inside the
exceptionhandlerpackage, create a new class namedGlobalExceptionHandler. - Add the following code to the
GlobalExceptionHandlerclass:
- Inside the
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): HandlesBookNotFoundExceptionand returns aResponseEntitywith error details and HTTP status404 Not Found.@ExceptionHandler(Exception.class): Handles all other exceptions and returns aResponseEntitywith error details and HTTP status500 Internal Server Error.
Test Using Postman Client
Step-by-Step Testing
-
Open Postman:
- Launch the Postman client from your installed applications.
-
Create a New Request:
- Click on "New" and then select "Request".
-
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).
- Set the request type to
-
Send Request:
- Click the "Send" button.
- Verify that the response status is
404 Not Foundand the response body contains the error details.
Explanation
- Request Type:
GETindicates 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.