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

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

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

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

  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Leveraging Salesforce Using Spring Boot
  • Spring Boot REST API Request Body Validation Example Using a Custom Validator
  • Spring Microservices RESTFul API Documentation With Swagger Part 1

Trending

  • How the Go Runtime Preempts Goroutines for Efficient Concurrency
  • Transforming AI-Driven Data Analytics with DeepSeek: A New Era of Intelligent Insights
  • Docker Model Runner: Streamlining AI Deployment for Developers
  • A Modern Stack for Building Scalable Systems
  1. DZone
  2. Coding
  3. Frameworks
  4. REST API Error Handling With Spring Boot

REST API Error Handling With Spring Boot

Want to learn more about room for improvement in error handling? Check out this tutorial on REST API error handling in Spring Boot.

By 
Ali Dehghani user avatar
Ali Dehghani
·
Updated Mar. 16, 20 · Tutorial
Likes (32)
Comment
Save
Tweet
Share
85.8K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

Spring Boot provides a great exception handling mechanism out of the box. The default implementation ofErrorController does a good job of catching and handling exceptions. Also, we still can define our own @ExceptionHandler s to catch and handle specific exceptions. But, there is still room for improvement:

  • Even if we decide to handle all exceptions using a custom @ExceptionHandler, some exceptions will still manage to escape that handler and theErrorController would be responsible for exception handling. This@ExceptionHandler vs. ErrorController duality can definitely be improved.
  • Sometimes, the default error representation looks a bit messy:
{
  "timestamp": "2018-09-23T15:05:32.681+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "NotBlank.dto.name",
        "NotBlank.name",
        "NotBlank.java.lang.String",
        "NotBlank"
      ],
      "arguments": [
        {
          "codes": [
            "dto.name",
            "name"
          ],
          "arguments": null,
          "defaultMessage": "name",
          "code": "name"
        }
      ],
      "defaultMessage": "{name.not_blank}",
      "objectName": "dto",
      "field": "name",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotBlank"
    }
  ],
  "message": "Validation failed for object='dto'. Error count: 1",
  "path": "/"
}


Sure, we can improve this by registering a custom ErrorAttributes implementation, but a more sensible and opinionated (and still easily customizable) default representation would be appreciated.

  • For validation errors, one can easily expose some arguments from the constraint to the interpolated message. For example, the minimum amount for an int value can be passed to the interpolated message using the {} placeholder syntax. But, the same is not true for other exceptions:
public class UserAlreadyExistsException extends RuntimeException {

    // How can I expose this value to the interpolated message?
    private final String username;

    // constructors and getters and setters
}


  • It would be nice if we had built-in support for Application-Level Error Codes for all exceptions. Sometimes, just by using an appropriate HTTP status code, we can't find out what exactly went wrong. For example, what if two totally different errors on the same endpoint have the same status code?

Room for Improvement

The errors-spring-boot-starter is an effort to provide a Bootiful, consistent, and opinionated approach to handle all sorts of exceptions. Built on top of Spring Boot's exception handling mechanism, the errors-spring-boot-starter offers:

  • A consistent approach to handle all exceptions — it doesn't matter if it's a validation/binding error or a custom domain-specific error or even a Spring related error. All of them would be handled by a WebErrorHandler implementation (no more ErrorController vs @ExceptionHandler)
  • Built-in support for application-specific error codes, again, for all possible errors.
  • Simple error message interpolation using MessageSource.
  • Customizable HTTP error representation.
  • Exposing arguments from exceptions to error messages.

Default Error Representation

By default, errors would be a JSON result with the following schema:

// For each error, there is a code/messsge combination in the errors array
{
  "errors": [
    {
      "code": "first_error_code",
      "message": "1st error message"
    }
  ]
}


In order to customize this representation, just register the HttpErrorAttributesAdapter implementation as a Spring Bean.

Consistent Error Handling Approach

All exceptions would be handled by a WebErrorHandlerimplementation. By default, this starter would consult a few built-in WebErrorHandler methods to handle the following particular exceptions:

  • An implementation to handle all validation/binding exceptions.
  • An implementation to handle custom exceptions annotated with the @ExceptionMapping.
  • An implementation to handle Spring MVC specific exceptions.
  • And if the Spring Security is on the classpath, an implementation to handle Spring Security specific exceptions.

You can easily register your own exception handler by implementing anWebErrorHandler implementation and registering it as a Spring Bean.

Built-in Error Code Support

Although using appropriate HTTP status codes is a recommended approach in RESTful APIs, sometimes, we need more information to find out what exactly went wrong. This is where Error Codes comes in. You can think of an error code as a Machine Readable description of the error. Each exception can be mapped to at least one error code.

The exception-to-error-code mappings vary based on the exception type:

  • Validation error codes will be extracted from the message attribute of the corresponding constraint annotation, e.g. @NotBlank(message = "name.required").
  • The errorCode attribute for exceptions annotated with the @ExceptionMapping looks like the following:
@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {}


  • Here is the code from the custom implementations of WebErrorHandler :
public class ExistedUserHandler implements WebErrorHandler {

    @Override
    public boolean canHandle(Throwable exception) {
        return exception instanceof UserAlreadyExistsException;
    }

    @Override
    public HandledException handle(Throwable exception) {   
        return new HandledException("user.already_exists", BAD_REQUEST, null);
    }
}


Exposing Arguments

As with Spring Boot, you can pass validation arguments from the constraint annotation, e.g. @Min(value = 18, message = "age.min"), to the to-be-interpolated message:

age.min = The minimum age is {0}!


In addition, to support this feature for validation errors, we extend it for custom exceptions using the @ExposeAsArg annotation. For example, if we're going to specify the already taken username in the following message:

user.already_exists=Another user with the '{0}' username already exists


You can write:

@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {
    @ExposeAsArg(0) private final String username;

    // constructor
}


Conclusion

In this article, we enumerated a few possible improvements over Spring Boot's exception handling approach by introducing the errors-spring-boot-starter starter package. For more information about this starter, check out this GitHub repository.

Spring Framework Spring Boot REST Web Protocols API Implementation Spring Security

Opinions expressed by DZone contributors are their own.

Related

  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Leveraging Salesforce Using Spring Boot
  • Spring Boot REST API Request Body Validation Example Using a Custom Validator
  • Spring Microservices RESTFul API Documentation With Swagger Part 1

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!