In modern web applications, providing clear and user-friendly error messages is crucial for a good user experience. In this blog post, we’ll explore how to customize error handling in a Spring Boot 3 application using Thymeleaf. We’ll look at how to create custom error attributes, define a global error controller, and design a custom error page with Thymeleaf.

Setting Up the Project

First, ensure your Spring Boot 3 project is set up with the necessary dependencies. We’ll need spring-boot-starter-thymeleaf for the Thymeleaf integration and spring-boot-starter-web for creating the web application.

Dependencies

Add the following dependencies to your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Customizing Error Attributes

Spring Boot allows you to customize the default error attributes by extending DefaultErrorAttributes. We can add additional information to our error responses as needed.

Step 1: Create CustomizedErrorAttributes

Create a new class named CustomizedErrorAttributes that extends DefaultErrorAttributes:

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

@Component
public class CustomizedErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);

        // Customize the error attributes
        errorAttributes.put("locale", webRequest.getLocale().toString());
        errorAttributes.put("customMessage", "This is a custom error message");

        Throwable error = getError(webRequest);
        if (error != null) {
            errorAttributes.put("exception", error.getClass().getName());
            errorAttributes.put("message", error.getMessage());
        }

        return errorAttributes;
    }
}

Step 2: Create a Global Error Controller

Next, create an error controller to handle the error path. This controller will render a custom error page when an error occurs.

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public String handleError(HttpServletRequest request, Model model) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        String errorMessage = (String) request.getAttribute("javax.servlet.error.message");

        model.addAttribute("statusCode", statusCode);
        model.addAttribute("errorMessage", errorMessage);

        return "customError"; // return custom error view
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }
}

Step 3: Create a Custom Error View

Create a Thymeleaf template named customError.html in the src/main/resources/templates directory to display custom error messages.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Error</title>
</head>
<body>
    <h1>Error</h1>
    <p>Status Code: <span th:text="${statusCode}">Unknown</span></p>
    <p>Error Message: <span th:text="${errorMessage}">Unknown</span></p>
    <p>Custom Message: This is a custom error message</p>
</body>
</html>

Step 4: Configure Application Properties

Configure the application properties to use the custom error path. Add the following to your application.properties:

server.error.path=/error

Step 5: Run Your Application

Start your Spring Boot application. Now, when an error occurs, the custom error page will be displayed, showing the status code and error message along with the custom message.

Also spring provides several ways to handle exceptions, including the use of @ExceptionHandler, @ControllerAdvice, and @ResponseStatus. Here’s how you can use these techniques to handle exceptions in your Spring Boot 3 application.

Using @ExceptionHandler

The @ExceptionHandler annotation is used to handle exceptions at the controller level. You can define a method in your controller to handle specific exceptions.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.ui.Model;

@Controller
public class HomeController {

    @GetMapping("/home")
    public String home(@RequestParam(value = "error", required = false) String error, Model model) {
        if (error != null) {
            throw new IllegalArgumentException("An error occurred");
        }
        model.addAttribute("message", "Welcome to Thymeleaf with Spring Boot!");
        return "home";
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgumentException(IllegalArgumentException ex, Model model) {
        model.addAttribute("errorMessage", ex.getMessage());
        return "error";
    }
}

In this example, the handleIllegalArgumentException method handles IllegalArgumentException exceptions thrown by the home method. It adds an error message to the model and returns the error.html view.

Using @ControllerAdvice

@ControllerAdvice is a more global approach to handling exceptions. It allows you to handle exceptions across multiple controllers.

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handleException(Exception ex, Model model) {
        model.addAttribute("errorMessage", ex.getMessage());
        return "error";
    }
}

In this example, the handleException method handles all exceptions thrown by any controller in the application. It adds an error message to the model and returns the error.html view.

Using @ResponseStatus

You can use the @ResponseStatus annotation to map exceptions to specific HTTP status codes.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {

    public BadRequestException(String message) {
        super(message);
    }
}

In this example, the BadRequestException is mapped to the HTTP status code 400 (Bad Request). When this exception is thrown, the client will receive a 400 status code.

Example Thymeleaf Template for Error Page

Create an error.html template in the src/main/resources/templates directory:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Error</title>
</head>
<body>
    <h1>Error</h1>
    <p th:text="${errorMessage}">An error occurred</p>
</body>
</html>

Putting It All Together

Here’s how you can combine these approaches in a Spring Boot 3 application:

pom.xml Dependencies

Make sure you have the necessary dependencies in your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

HomeController.java

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HomeController {

    @GetMapping("/home")
    public String home(@RequestParam(value = "error", required = false) String error, Model model) {
        if (error != null) {
            throw new IllegalArgumentException("An error occurred");
        }
        model.addAttribute("message", "Welcome to Thymeleaf with Spring Boot!");
        return "home";
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgumentException(IllegalArgumentException ex, Model model) {
        model.addAttribute("errorMessage", ex.getMessage());
        return "error";
    }
}

GlobalExceptionHandler.java

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handleException(Exception ex, Model model) {
        model.addAttribute("errorMessage", ex.getMessage());
        return "error";
    }
}

BadRequestException.java

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {

    public BadRequestException(String message) {
        super(message);
    }
}

error.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Error</title>
</head>
<body>
    <h1>Error</h1>
    <p th:text="${errorMessage}">An error occurred</p>
</body>
</html>

With this setup, your Spring Boot 3 application will be able to handle exceptions effectively, providing meaningful error messages to users while maintaining a robust and secure application.