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
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

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
  • Secure Spring Boot 3 Application With Keycloak

Trending

  • Architecting Zero-Trust AI Agents: How to Handle Data Safely
  • How SaaS Architectures Break at Scale — and the Engineering Decisions That Prevent It
  • Mocking Kafka for Local Spring Development
  • Your AI Agent Tests Are Passing, But Your Agent Is Still Broken
  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
86.3K 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
  • Secure Spring Boot 3 Application With Keycloak

Partner Resources

×

Comments

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

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

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 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook