DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • How To Build Web Service Using Spring Boot 2.x
  • How To Run the Spring Boot Application as a Stand-Alone Java Application

Trending

  • A Guide to Developing Large Language Models Part 1: Pretraining
  • Hybrid Cloud vs Multi-Cloud: Choosing the Right Strategy for AI Scalability and Security
  • Beyond Linguistics: Real-Time Domain Event Mapping with WebSocket and Spring Boot
  • Optimize Deployment Pipelines for Speed, Security and Seamless Automation
  1. DZone
  2. Coding
  3. Frameworks
  4. Spring Boot Exception Handling

Spring Boot Exception Handling

In this article, we are going to learn how to configure Spring Boot to handle exceptions, including custom ones, and customize the error responses and handling.

By 
Seun Matt user avatar
Seun Matt
DZone Core CORE ·
Nov. 22, 21 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
14.7K Views

Join the DZone community and get the full member experience.

Join For Free

1. Overview

Exceptions are undesired behavior of a software application caused by faulty logic. In this article, we're going to be looking at how to handle exceptions in a Spring Boot application.

What we want to achieve is that whenever there's an error in our application, we want to gracefully handle it and return the following response format:

Listing 1.1 error response format 

JSON
 
{
  "code": 400,
  "message": "Missing required fields",
  "errors": [
    "additional specific error"
    "username is required",
    "email is required"
  ],
  "status": false
}


The code is a standard HTTP status code and the status attribute is a simple way to know if the request is successful or not. The response message is a summary of the failures while the errors array contains more specific and detailed error messages.

2. Basic Exception Handling

We will create a class GlobalExceptionHandler that will implement the ErrorController interface and define a controller action for the /error endpoint. We will annotate the class with @RestController for Spring Boot to recognize our error endpoint.

Listing 2.1 GlobalExceptionHandler.java

Java
 
@RestController
public class GlobalExceptionHandler implements ErrorController {

    public GlobalExceptionHandler() {
    }

    @RequestMapping("/error")
    public ResponseEntity<Map<String, Object>> handleError(HttpServletRequest request) {
        HttpStatus httpStatus = getHttpStatus(request);
        String message = getErrorMessage(request, httpStatus);

        Map<String, Object> response = new HashMap<>();
        response.put("status", false);
        response.put("code", httpStatus.value());
        response.put("message", message);
        response.put("errors", Collections.singletonList(message));

        return ResponseEntity.status(httpStatus).body(response);
    }

    private HttpStatus getHttpStatus(HttpServletRequest request) {

        //get the standard error code set by Spring Context
        Integer status = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        if (status != null) {
            return HttpStatus.valueOf(status);
        }

        // maybe we're the one that trigger the redirect
        // with the code param
        String code = request.getParameter("code");
        if (code != null && !code.isBlank()) {
            return HttpStatus.valueOf(code);
        }

        //default fallback
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }

    private String getErrorMessage(HttpServletRequest request, HttpStatus httpStatus) {

        //get the error message set by Spring context
        // and return it if it's not null
        String message = (String) request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
        if (message != null && !message.isEmpty()) {
            return message;
        }

        //if the default message is null,
        //let's construct a message based on the HTTP status
        switch (httpStatus) {
            case NOT_FOUND:
                message = "The resource does not exist";
                break;
            case INTERNAL_SERVER_ERROR:
                message = "Something went wrong internally";
                break;
            case FORBIDDEN:
                message = "Permission denied";
                break;
            case TOO_MANY_REQUESTS:
                message = "Too many requests";
                break;
            default:
                message = httpStatus.getReasonPhrase();
        }

        return message;
    }

}


We created two helper functions — getHttpStatus and getErrorMessage. The first one will extract the HTTP status from the Servlet request while the second function will extrapolate the error message from either the Servlet request or the HTTP status.

The handleError function will be called whenever there's a runtime error in the application. The function will use the two helper methods to get the code and message to return as part of the final response.

Let's run the application and use curl to test our setup. We will simply visit an endpoint that does not exist:

Java
 
curl --location --request GET 'http://localhost:8080/not/found'

{
  "code": 404,
  "message": "The resource does not exist",
  "errors": [
    "The resource does not exist"
  ],
  "status": false
}


Our application is now returning our custom response. Let's add a new controller action that will raise a RuntimeException with a custom message and see what the response will be when we call it.

Listing 2.2 IndexController.java

Java
 
@RestController
public class IndexController {

    @GetMapping("/ex/runtime")
    public ResponseEntity<Map<String, Object>> runtimeException() {
        throw new RuntimeException("RuntimeException raised");
    }

}


Java
 
curl --location --request GET 'http://localhost:8080/ex/runtime' -v

*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /ex/runtime HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Tue, 16 Nov 2021 07:10:10 GMT
< Connection: close
< 
* Closing connection 0

{
  "code": 500,
  "message": "Something went wrong internally",
  "errors": [
    "Something went wrong internally"
  ],
  "status": false
}


This time around, we appended the -v flag to the curl command and we can see from the verbose response that the HTTP code returned is indeed 500 — the same as the value of code in the returned response body.

3. Handling Specific Exception Class

Even though what we have is capable of handling all exceptions, we can still have specific handlers for specific exception classes.

At times we want to handle certain exception classes because we want to respond differently and/or execute custom logic. 

To achieve this, we will annotate the GlobalExceptionHandler class with @RestControllerAdvice and define exception handler methods for each exception class we want to handle.

For the purpose of this article, we will handle the HttpRequestMethodNotSupportedException class and return a custom message.

Listing 3.1 GlobalExceptionHandler.java

Java
 
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<Map<String, Object>> handleError(HttpRequestMethodNotSupportedException e) {
    String message = e.getMessage();
    Map<String, Object> response = new HashMap<>();
    response.put("status", false);
    response.put("code", HttpStatus.METHOD_NOT_ALLOWED);
    response.put("message", "It seems you're using the wrong HTTP method");
    response.put("errors", Collections.singletonList(message));
    return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(response);
}


Now, if we call the /ex/runtime endpoint with a POST method, we should get the unique message that we set and the errors array will contain the raw exception message:

Java
 
curl --location --request POST 'http://localhost:8080/ex/runtime'

{
  "code": 405,
  "message": "It seems you're using the wrong HTTP method",
  "errors": [
    "Request method 'POST' not supported"
  ],
  "status": false
}


We can repeat this for as many as possible exception classes that we want to handle specifically. Note that declaring a specific handler means the /error endpoint will not be invoked for that particular exception

4. Handling a Custom Exception Class

Simply put, we will create a subclass of the RuntimeException class and create a specific handler for it in the GlobalExceptionHandler. Whenever we want to return an error response to our API client, we will just raise a new instance of our custom exception class.

The sweet part is that we can throw the exception from a controller, a service, or just about any other component and it will be handled correctly.

First, let's create the custom exception class.

Listing 4.1 CustomApplicationException.java

Java
 
public class CustomApplicationException extends RuntimeException {

    private HttpStatus httpStatus;
    private List<String> errors;
    private Object data;

    public CustomApplicationException(String message) {
        this(HttpStatus.BAD_REQUEST, message);
    }

    public CustomApplicationException(String message, Throwable throwable) {
        super(message, throwable);
    }

    public CustomApplicationException(HttpStatus httpStatus, String message) {
        this(httpStatus, message, Collections.singletonList(message), null);
    }

    public CustomApplicationException(HttpStatus httpStatus, String message, Object data) {
        this(httpStatus, message, Collections.singletonList(message), data);
    }

    public CustomApplicationException(HttpStatus httpStatus, String message, List<String> errors) {
        this(httpStatus, message, errors, null);
    }

    public CustomApplicationException(HttpStatus httpStatus, String message, List<String> errors, Object data) {
        super(message);
        this.httpStatus = httpStatus;
        this.errors = errors;
        this.data = data;
    }

    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    public List<String> getErrors() {
        return errors;
    }

    public Object getData() {
        return data;
    }
}


We defined a number of useful fields for the CustomApplicationException class alongside convenient constructors. This means we can specify the HTTP status, message, and list of errors when we're raising the exception.

Now we will define a handler for it and create a controller endpoint to test it out.

Listing 4.2 GlobalExceptionHandler.java 

Java
 
@ExceptionHandler(CustomApplicationException.class)
public ResponseEntity<Map<String, Object>> handleError(CustomApplicationException e) {
    Map<String, Object> response = new HashMap<>();
    response.put("status", false);
    response.put("code", e.getHttpStatus().value());
    response.put("message", e.getMessage());
    response.put("errors", e.getErrors());
    return ResponseEntity.status(e.getHttpStatus()).body(response);
}


Listing 4.3 IndexController.java

Java
 
@PostMapping("/ex/custom")
public ResponseEntity<Map<String, Object>> customException(@RequestBody Map<String, Object> request) {
    List<String> errors = new ArrayList<>();
    if(!request.containsKey("username"))
        errors.add("Username is required");
    if(!request.containsKey("password"))
        errors.add("Password is required");

    if(!errors.isEmpty()) {
        String errorMessage = "Missing required parameters";
        throw new CustomApplicationException(HttpStatus.BAD_REQUEST, errorMessage , errors);
    }

    return ResponseEntity.ok(Collections.singletonMap("status", true));
}


Java
 
curl --location --request POST 'http://localhost:8080/ex/custom' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "john"
}'


{
    "code": 400,
    "message": "Missing required parameters",
    "errors": [
        "Password is required"
    ],
    "status": false
}


The returned message is a general description of what went wrong while the errors contain the exact field that's missing — just as we wanted.

5. Conclusion

We've looked at how to configure Spring Boot to handle different types of exceptions and return the desired response. In the next article, we're going to look at how we can apply these techniques to a monolith application and return HTML templates/responses.

The complete source code is available on GitHub.

Spring Framework Spring Boot Listing (computer) application Java (programming language)

Published at DZone with permission of Seun Matt. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements
  • How To Build Web Service Using Spring Boot 2.x
  • How To Run the Spring Boot Application as a Stand-Alone Java Application

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!