Error Handling Spring Boot Starter

repository·develop·Indexed 19 days ago

https://github.com/wimdeblauwe/error-handling-spring-boot-starter

A Spring Boot starter designed to simplify and standardize error handling for REST APIs. It ensures consistent error response formats by automatically translating exceptions into JSON responses containing error codes and messages. The library provides specialized handling for validation exceptions (MethodArgumentNotValidException, ConstraintViolationException), configurable HTTP status codes, customizable error codes and messages, and integration options for Spring Security's UnauthorizedEntryPoint and AccessDeniedHandler.

Tokens
6.5K
Snippets
21
Records
22
Agent score
18%

What's inside error-handling-spring-boot-starter

  1. How validation exception handling works

    develop

    The library provides specialized handling for validation exceptions like MethodArgumentNotValidException and ConstraintViolationException. When validation fails, the JSON response includes a code (VALIDATION_FAILED), a message summarizing the errors, and arrays for fieldErrors, parameterErrors, or globalErrors depending on where the validation occurred.

    • fieldErrors: Errors on specific properties of a request body.
    • parameterErrors: Errors on @RequestParam or other method parameters.
    • globalErrors: Errors on the class level (e.g., custom class-level constraints).
    {
      "code": "VALIDATION_FAILED",
      "message": "Validation failed for object='exampleRequestBody'. Error count: 2",
      "fieldErrors": [
        {
          "code": "INVALID_SIZE",
          "property": "name",
          "message": "size must be between 10 and 2147483647",
          "rejectedValue": "",
          "path": "name"
        }
      ]
    }
  2. How default Exception handling works

    develop

    Once the library is on the classpath, it automatically registers an @ControllerAdvice bean. When a custom exception is thrown from a @RestController method, the library generates a JSON response containing a code (derived from the exception class name) and a message (the exception's message). By default, the HTTP status code is 500 Internal Server Error.

    {
      "code": "USER_NOT_FOUND",
      "message": "Could not find user with id 123"
    }
  3. Configure Spring Security UnauthorizedEntryPoint

    develop

    By default, the library does not provide a response for unauthorized exceptions. To use the library's error handling for authentication failures, you must define io.github.wimdeblauwe.errorhandlingspringbootstarter.UnauthorizedEntryPoint as a Spring bean and set it as the entry point in your SecurityFilterChain.

    import io.github.wimdeblauwe.errorhandlingspringbootstarter.UnauthorizedEntryPoint;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.mapper.ErrorCodeMapper;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.mapper.ErrorMessageMapper;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.mapper.HttpStatusMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.web.SecurityFilterChain;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class WebSecurityConfiguration {
        @Bean
        public UnauthorizedEntryPoint unauthorizedEntryPoint(HttpStatusMapper httpStatusMapper, 
                                                             ErrorCodeMapper errorCodeMapper, 
                                                             ErrorMessageMapper errorMessageMapper, 
                                                             ObjectMapper objectMapper) {
            return new UnauthorizedEntryPoint(httpStatusMapper, errorCodeMapper, errorMessageMapper, objectMapper);
        }
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http, UnauthorizedEntryPoint unauthorizedEntryPoint) throws Exception {
            http.httpBasic(customizer -> customizer.disable());
            http.authorizeHttpRequests(customizer -> customizer.anyRequest().authenticated());
            http.exceptionHandling(customizer -> customizer.authenticationEntryPoint(unauthorizedEntryPoint));
            return http.build();
        }
    }
  4. Install Error Handling Spring Boot Starter

    develop

    Add the library as a dependency to your Spring Boot project. This library is intended specifically for Spring Boot and will not work outside of it.

    Maven

    <dependency>
        <groupId>io.github.wimdeblauwe</groupId>
        <artifactId>error-handling-spring-boot-starter</artifactId>
        <version>LATEST_VERSION_HERE</version>
    </dependency>

    Gradle

    compile 'io.github.wimdeblauwe:error-handling-spring-boot-starter:LATEST_VERSION_HERE'
    <dependency>
        <groupId>io.github.wimdeblauwe</groupId>
        <artifactId>error-handling-spring-boot-starter</artifactId>
        <version>LATEST_VERSION_HERE</version>
    </dependency>
  5. Handle non-RestController exceptions

    develop

    The library defaults to only handling exceptions from @RestController classes. To support exceptions that occur before reaching a controller (like HttpRequestMethodNotSupportedException), you must define a custom @ControllerAdvice that extends io.github.wimdeblauwe.errorhandlingspringbootstarter.servlet.ErrorHandlingControllerAdvice.

    import io.github.wimdeblauwe.errorhandlingspringbootstarter.servlet.ErrorHandlingControllerAdvice;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.ApiExceptionHandler;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.FallbackApiExceptionHandler;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.LoggingService;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import java.util.List;
    
    @ControllerAdvice
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    public class FallbackExceptionHandler extends ErrorHandlingControllerAdvice {
    
        public FallbackExceptionHandler(List<ApiExceptionHandler> handlers, 
                                        FallbackApiExceptionHandler fallbackHandler, 
                                        LoggingService loggingService) {
            super(handlers, fallbackHandler, loggingService);
        }
    }
  6. Document error responses in OpenAPI/Swagger

    develop

    To ensure error responses are visible in your Swagger/OpenAPI documentation, use the @ApiResponse annotation on your controller methods. You should point the schema to ApiErrorResponse.class to represent the standardized error structure.

    @GetMapping("/{id}")
    @ApiResponse(
        responseCode = "404",
        content = @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = ApiErrorResponse.class)
        )
    )
    public ResponseEntity<String> getExample(@PathVariable String id) {
        return ResponseEntity.ok("Example response");
    }
  7. Configure Spring Security AccessDeniedHandler

    develop

    To handle access denied exceptions using the library, define io.github.wimdeblauwe.errorhandlingspringbootstarter.ApiErrorResponseAccessDeniedHandler as a Spring bean and register it in your SecurityFilterChain.

    import io.github.wimdeblauwe.errorhandlingspringbootstarter.ApiErrorResponseAccessDeniedHandler;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.mapper.ErrorCodeMapper;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.mapper.ErrorMessageMapper;
    import io.github.wimdeblauwe.errorhandlingspringbootstarter.mapper.HttpStatusMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class WebSecurityConfiguration {
    
        @Bean
        public AccessDeniedHandler accessDeniedHandler(HttpStatusMapper httpStatusMapper, 
                                                        ErrorCodeMapper errorCodeMapper, 
                                                        ErrorMessageMapper errorMessageMapper, 
                                                        ObjectMapper objectMapper) {
            return new ApiErrorResponseAccessDeniedHandler(objectMapper, httpStatusMapper, errorCodeMapper, errorMessageMapper);
        }
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http, AccessDeniedHandler accessDeniedHandler) throws Exception {
            http.httpBasic(customizer -> customizer.disable());
            http.authorizeHttpRequests(customizer -> customizer.anyRequest().authenticated());
            http.exceptionHandling(customizer -> customizer.accessDeniedHandler(accessDeniedHandler));
            return http.build();
        }
    }
  8. Configure error response JSON field names

    develop

    You can rename the standard keys in the error JSON response using the following properties:

    error.handling.json-field-names.code=errorCode
    error.handling.json-field-names.message=description
    error.handling.json-field-names.field-errors=fieldFailures
    error.handling.json-field-names.global-errors=classFailures
    error.handling.json-field-names.code=errorCode
    error.handling.json-field-names.message=description
  9. Configure error codes

    develop

    The library generates error codes based on the exception class name. You can customize this behavior:

    • Change Strategy: Set error.handling.default-error-code-strategy=FULL_QUALIFIED_NAME to use the full class name instead of the default ALL_CAPS style.
    • Global Override: Use error.handling.codes.<fully-qualified-exception-name>=<CUSTOM_CODE> to override codes for specific exceptions globally.
    • Per-Class Override: Use the @ResponseErrorCode("CUSTOM_CODE") annotation on your exception class.
    • Validation Overrides: Override specific validation annotation codes using error.handling.codes.<AnnotationName>=<CODE> (e.g., error.handling.codes.Size=SIZE_REQUIREMENT_NOT_MET).
    • Field-Specific Validation Overrides: Use error.handling.codes.<fieldName>.<AnnotationName>=<CODE> to target a specific field (e.g., error.handling.codes.password.Pattern=PASSWORD_COMPLEXITY_REQUIREMENTS_NOT_MET).
    error.handling.default-error-code-strategy=FULL_QUALIFIED_NAME
    error.handling.codes.java.lang.IllegalArgumentException=ILLEGAL_ARGUMENT
    error.handling.codes.password.Pattern=PASSWORD_COMPLEXITY_REQUIREMENTS_NOT_MET
  10. Configure error messages

    develop

    Override the default exception message in the JSON response using the error.handling.messages property:

    • Exception Override: error.handling.messages.<fully-qualified-exception-name>=<Custom Message>
    • Validation Annotation Override: error.handling.messages.<AnnotationName>=<Custom Message> (e.g., error.handling.messages.NotBlank=The property should not be blank).
    • Field-Specific Override: error.handling.messages.<fieldName>.<AnnotationName>=<Custom Message> (e.g., error.handling.messages.password.Pattern=The password complexity rules are not met.).

    Superclass Hierarchy Search: If error.handling.search-super-class-hierarchy=true is set, the library will search up the exception hierarchy for matching configuration. You can reset messaging to default for a specific subclass by providing an empty value: error.handling.messages.my.ApplicationException=.

    error.handling.messages.com.company.application.user.UserNotFoundException=The user was not found
    error.handling.messages.NotBlank=The property should not be blank
    error.handling.messages.password.Pattern=The password complexity rules are not met.