{"id":25776067,"url":"https://github.com/alimate/errors-spring-boot-starter","last_synced_at":"2025-02-27T06:05:29.593Z","repository":{"id":33597974,"uuid":"148058175","full_name":"alimate/errors-spring-boot-starter","owner":"alimate","description":"Elegant Error Handling for Spring Boot","archived":true,"fork":false,"pushed_at":"2022-03-31T18:42:19.000Z","size":305,"stargazers_count":267,"open_issues_count":17,"forks_count":56,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-11-15T02:58:47.486Z","etag":null,"topics":["error-handling","java","spring","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alimate.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-09T19:22:35.000Z","updated_at":"2024-10-11T13:05:39.000Z","dependencies_parsed_at":"2022-09-01T01:42:16.313Z","dependency_job_id":null,"html_url":"https://github.com/alimate/errors-spring-boot-starter","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alimate%2Ferrors-spring-boot-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alimate%2Ferrors-spring-boot-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alimate%2Ferrors-spring-boot-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alimate%2Ferrors-spring-boot-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alimate","download_url":"https://codeload.github.com/alimate/errors-spring-boot-starter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240987435,"owners_count":19889333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["error-handling","java","spring","spring-boot"],"created_at":"2025-02-27T06:01:17.143Z","updated_at":"2025-02-27T06:05:29.585Z","avatar_url":"https://github.com/alimate.png","language":"Java","readme":"\u003cp align=\"center\"\u003e\n    \u003cimg width=\"300\" height=\"300\" src=\"https://imgur.com/cOcB3kB.png\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eErrors Spring Boot Starter\u003c/h1\u003e \n\n[![Build Status](https://travis-ci.org/alimate/errors-spring-boot-starter.svg?branch=master)](https://travis-ci.org/alimate/errors-spring-boot-starter) \n[![codecov](https://codecov.io/gh/alimate/errors-spring-boot-starter/branch/master/graph/badge.svg)](https://codecov.io/gh/alimate/errors-spring-boot-starter) \n[![Maven Central](https://img.shields.io/maven-central/v/me.alidg/errors-spring-boot-starter.svg)](https://search.maven.org/search?q=g:me.alidg%20AND%20a:errors-spring-boot-starter) \n[![Javadocs](http://www.javadoc.io/badge/me.alidg/errors-spring-boot-starter.svg)](http://www.javadoc.io/doc/me.alidg/errors-spring-boot-starter) \n[![Sonatype](https://img.shields.io/static/v1.svg?label=snapshot\u0026message=v1.5.0-SNAPSHOT\u0026color=blueviolet)](https://oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots\u0026g=me.alidg\u0026a=errors-spring-boot-starter\u0026v=1.5.0-SNAPSHOT\u0026e=jar) \n[![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/alimate_errors-spring-boot-starter?label=code%20quality\u0026server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=alimate_errors-spring-boot-starter)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\n\u003cp align=\"center\"\u003e\u003cb\u003eA Bootiful, Consistent and Opinionated Approach to Handle all sorts of Exceptions.\u003c/b\u003e\u003c/p\u003e\n\n## Table of Contents\n\n  * [Make Error Handling Great Again!](#make-error-handling-great-again)\n  * [Getting Started](#getting-started)\n    + [Download](#download)\n    + [Prerequisites](#prerequisites)\n    + [Overview](#overview)\n    + [Error Codes](#error-codes)\n    + [Error Message](#error-message)\n    + [Exposing Arguments](#exposing-arguments)\n      + [Exposing Named Arguments](#exposing-named-arguments)\n      + [Named Arguments Interpolation](#named-arguments-interpolation)\n    + [Validation and Binding Errors](#validation-and-binding-errors)\n    + [Custom Exceptions](#custom-exceptions)\n    + [Spring MVC](#spring-mvc)\n    + [Spring Security](#spring-security)\n      + [Reactive](#reactive-security)\n      + [Servlet](#servlet-security)\n    + [Error Representation](#error-representation)\n      + [Fingerprinting](#fingerprinting)\n      + [Customizing the Error Representation](#customizing-the-error-representation)\n    + [Default Error Handler](#default-error-handler)\n    + [Refining Exceptions](#refining-exceptions)\n    + [Logging Exceptions](#logging-exceptions)\n    + [Post Processing Handled Exceptions](#post-processing-handled-exceptions)\n    + [Registering Custom Handlers](#registering-custom-handlers)\n    + [Test Support](#test-support)\n  * [Appendix](#appendix)\n    + [Configuration](#configuration)\n  * [License](#license)\n\n## Make Error Handling Great Again!\nBuilt on top of Spring Boot's great exception handling mechanism, the `errors-spring-boot-starter` offers:\n - A consistent approach to handle all exceptions. Doesn't matter if it's a validation/binding error or a \n custom domain-specific error or even a Spring related error, All of them would be handled by a `WebErrorHandler`\n implementation (No more `ErrorController` vs `@ExceptionHandler` vs `WebExceptionHandler`)\n - Built-in support for application specific error codes, again, for all possible errors.\n - Simple error message interpolation using plain old `MessageSource`s.\n - Customizable HTTP error representation.\n - Exposing arguments from exceptions to error messages.\n - Supporting both traditional and reactive stacks.\n - Customizable exception logging.\n - Supporting error fingerprinting.\n\n## Getting Started\n\n### Download\n\nDownload the [latest JAR](https://search.maven.org/remotecontent?filepath=me/alidg/errors-spring-boot-starter/1.4.0/errors-spring-boot-starter-1.4.0.jar) or grab via Maven:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eme.alidg\u003c/groupId\u003e\n    \u003cartifactId\u003eerrors-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.4.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nor Gradle:\n```\ncompile \"me.alidg:errors-spring-boot-starter:1.4.0\"\n```\n\nIf you like to stay at the cutting edge, use our `1.5.0-SNAPSHOT` version. Of course you should define the following \nsnapshot repository:\n```xml\n\u003crepositories\u003e\n    \u003crepository\u003e\n        \u003cid\u003eSonatype\u003c/id\u003e\n        \u003curl\u003ehttps://oss.sonatype.org/content/repositories/snapshots/\u003c/url\u003e\n    \u003c/repository\u003e\n\u003c/repositories\u003e\n```\nor:\n```groovy\nrepositories {\n    maven {\n      url 'https://oss.sonatype.org/content/repositories/snapshots/'\n    }\n}\n```\n\n### Prerequisites\nThe main dependency is JDK 8+. Tested with:\n - JDK 8, JDK 9, JDK 10 and JDK 11 on Linux.\n - Spring Boot `2.2.0.RELEASE` (Also, should work with any `2.0.0+`)\n\n### Overview\nThe `WebErrorHandler` implementations are responsible for handling different kinds of exceptions. When an exception \nhappens, the `WebErrorHandlers` (A factory over all `WebErrorHandler` implementations) catches the exception and would \nfind an appropriate implementation to handle the exception. By default, `WebErrorHandlers` consults with the\nfollowing implementations to handle a particular exception:\n - An implementation to handle all validation/binding exceptions.\n - An implementation to handle custom exceptions annotated with the `@ExceptionMapping`.\n - An implementation to handle Spring MVC specific exceptions.\n - And if the Spring Security is on the classpath, An implementation to handle Spring Security specific exceptions.\n\nAfter delegating to the appropriate handler, the `WebErrorHandlers` turns the handled exception result into a `HttpError`,\nwhich encapsulates the HTTP status code and all error code/message combinations.\n\n### Error Codes\nAlthough using appropriate HTTP status codes is a recommended approach in RESTful APIs, sometimes, we need more information \nto find out what exactly went wrong. This is where *Error Codes* comes in. You can think of an error code as a *Machine Readable* \ndescription of the error. Each exception can be mapped to **at least one** error code.\n\nIn `errors-spring-boot-starter`, one can map exceptions to error codes in different ways:\n - Validation error codes can be extracted from the *Bean Validation*'s constraints:\n     ```java\n     public class User {  \n    \n         @NotBlank(message = \"username.required\")\n         private final String username;\n      \n         @NotBlank(message = \"password.required\")\n         @Size(min = 6, message = \"password.min_length\")\n         private final String password;\n      \n         // constructor and getter and setters\n     }\n     ```\n    To report a violation in password length, the `password.min_length` would be reported as the error code. As you may guess,\n    one validation exception can contain multiple error codes to report all validation violations at once.\n    \n - Specifying the error code for custom exceptions using the `@ExceptionMapping` annotation:\n    ```java\n    @ExceptionMapping(statusCode = BAD_REQUEST, errorCode = \"user.already_exists\")\n    public class UserAlreadyExistsException extends RuntimeException {}\n    ```\n    The `UserAlreadyExistsException` exception would be mapped to `user.already_exists` error code.\n \n - Specifying the error code in a `WebErrorHandler` implementation:\n    ```java\n    public class ExistedUserHandler implements WebErrorHandler {\n    \n        @Override\n        public boolean canHandle(Throwable exception) {\n            return exception instanceof UserAlreadyExistsException;\n        }\n     \n        @Override\n        public HandledException handle(Throwable exception) {   \n            return new HandledException(\"user.already_exists\", BAD_REQUEST, null);\n        }\n    }\n    ```\n\n### Error Message\nOnce the exception mapped to error code(s), we can add a companion and *Human Readable* error message. This can be done\nby registering a Spring `MessageSource` to perform the *code-to-message* translation. For example, if we add the following\nkey-value pair in our message resource file:\n```properties\nuser.already_exists=Another user with the same username already exists\n```\nThen if an exception of type `UserAlreadyExistsException` was thrown, you would see a `400 Bad Request` HTTP response \nwith a body like:\n```json\n{\n  \"errors\": [\n    {\n      \"code\": \"user.already_exists\",\n      \"message\": \"Another user with the same username already exists\"\n    }\n  ]\n}\n```\nSince `MessageSource` supports Internationalization (i18n), our error messages can possibly have different values based\non each *Locale*.\n\n### Exposing Arguments\nWith *Bean Validation* you can pass parameters from the constraint validation, e.g. `@Size`, to its corresponding \ninterpolated message. For example, if we have:\n```properties\npassword.min_length=The password must be at least {0} characters\n```\nAnd a configuration like:\n```java\n@Size(min = 6, message = \"password.min_length\")\nprivate final String password;\n```\nThe `min` attribute from the `@Size` constraint would be passed to the message interpolation mechanism, so:\n```json\n{\n  \"errors\": [\n    {\n      \"code\": \"password.min_length\",\n      \"message\": \"The password must be at least 6 characters\"\n    }\n  ]\n}\n```\nIn addition to support this feature for validation errors, we extend it for custom exceptions using the `@ExposeAsArg`\nannotation. For example, if we're going to specify the already taken username in the message:\n```properties\nuser.already_exists=Another user with the '{0}' username already exists\n```\nWe could write:\n```java\n@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = \"user.already_exists\")\npublic class UserAlreadyExistsException extends RuntimeException {\n    @ExposeAsArg(0) private final String username;\n    \n    // constructor\n}\n```\nThen the `username` property from the `UserAlreadyExistsException` would be available to the message under the \n`user.already_exists` key as the first argument. `@ExposeAsArg` can be used on fields and no-arg methods with a\nreturn type. The `HandledException` class also accepts the *to-be-exposed* arguments in its constructor.\n\n#### Exposing Named Arguments\nBy default error arguments will be used in message interpolation only. It is also possible to additionally get those\narguments in error response by defining the configuration property `errors.expose-arguments`.\nWhen enabled, you might get the following response payload:\n```json\n{\n  \"errors\": [\n    {\n      \"code\": \"password.min_length\",\n      \"message\": \"The password must be at least 6 characters\",\n      \"arguments\": {\n        \"min\": 6\n      }\n    }\n  ]\n}\n```\n\nThe `errors.expose-arguments` property takes 3 possible values:\n - `NEVER` - named arguments will never be exposed. This is the default setting.\n - `NON_EMPTY` - named arguments will be exposed only in case there are any. If error has no arguments,\n   result payload will not have `\"arguments\"` element.\n - `ALWAYS` - the `\"arguments\"` element is always present in payload, even when the error has no arguments.\n   In that case empty map will be provided: `\"arguments\": {}`.\n   \nCheckout [here](EXPOSED-ARGS.md) for more detail on how we expose arguments for different exception categories.\n\n#### Named Arguments Interpolation\nYou can use either positional or named argument placeholders in message templates. Given:\n```java\n@Size(min = 6, max = 20, message = \"password.length\")\nprivate final String password;\n```\nYou can create message template in `messages.properties` with positional arguments:\n```properties\npassword.length=Password must have length between {1} and {0}\n```\nArguments are sorted by name. Since lexicographically `max` \u003c `min`, placeholder `{0}` will be substituted\nwith argument `max`, and `{1}` will have value of argument `min`.\n\nYou can also use argument names as placeholders:\n```properties\npassword.length=Password must have length between {min} and {max}\n```\nNamed arguments interpolation works out of the box, regardless of the `errors.expose-arguments` value.\nYou can mix both approaches, but it is not recommended.\n\nIf there is a value in the message that *should not* be interpolated, escape the first `{` character with a backslash:\n\n```properties\npassword.length=Password \\\\{min} is {min} and \\\\{max} is {max}\n```\nAfter interpolation, this message would read: `Password {min} is 6 and {max} is 20`.\n\nArguments annotated with `@ExposeAsArg` will be named by annotated field or method name:\n```java\n@ExposeAsArg(0)\nprivate final String argName; // will be exposed as \"argName\"\n```\nThis can be changed by the `name` parameter:\n```java\n@ExposeAsArg(value = 0, name = \"customName\")\nprivate final String argName; // will be exposed as \"customName\"\n```\n\n### Validation and Binding Errors\nValidation errors can be processed as you might expect. For example, if a client passed an empty JSON to a controller method\nlike:\n```java\n@PostMapping\npublic void createUser(@RequestBody @Valid User user) {\n    // omitted\n}\n```\nThen the following error would be returned:\n```json\n{\n  \"errors\": [\n    {\n      \"code\": \"password.min_length\",\n      \"message\": \"corresponding message!\"\n    },\n    {\n       \"code\": \"password.required\",\n       \"message\": \"corresponding message!\"\n    },\n    {\n      \"code\": \"username.required\",\n      \"message\": \"corresponding message!\"\n    }\n  ]\n}\n```\nBean Validation's `ConstraintViolationException`s will be handled in the same way, too.\n\n### Custom Exceptions\nCustom exceptions can be mapped to status code and error code combination using the `@ExceptionMapping` annotation:\n```java\n@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = \"user.already_exists\")\npublic class UserAlreadyExistsException extends RuntimeException {}\n```\nHere, every time we catch an instance of `UserAlreadyExistsException`, a `Bad Request` HTTP response with `user.already_exists`\nerror would be returned.\n\nAlso, it's possible to expose some arguments from custom exceptions to error messages using the `ExposeAsArg`:\n```java\n@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = \"user.already_exists\")\npublic class UserAlreadyExistsException extends RuntimeException {\n    @ExposeAsArg(0) private final String username;\n    \n    // constructor\n    \n    @ExposeAsArg(1)\n    public String exposeThisToo() {\n        return \"42\";\n    }\n}\n```\nThen the error message template can be something like:\n```properties\nuser.already_exists=Another user exists with the '{0}' username: {1}\n```\nDuring message interpolation, the `{0}` and `{1}` placeholders would be replaced with annotated field's value and\nmethod's return value. The `ExposeAsArg` annotation is applicable to:\n - Fields\n - No-arg methods with a return type\n\n### Spring MVC\nBy default, a custom `WebErrorHandler` is registered to handle common exceptions thrown by Spring MVC:\n\n|                 Exception                 | Status Code |           Error Code          |               Exposed Args               |\n|:-----------------------------------------:|:-----------:|:-----------------------------:|:----------------------------------------:|\n|     `HttpMessageNotReadableException`     |     400     | `web.invalid_or_missing_body` |                     -                    |\n|   `HttpMediaTypeNotAcceptableException`   |     406     |      `web.not_acceptable`     |       List of acceptable MIME types      |\n|    `HttpMediaTypeNotSupportedException`   |     415     |  `web.unsupported_media_type` |       The unsupported content type       |\n|  `HttpRequestMethodNotSupportedException` |     405     |    `web.method_not_allowed`   |          The invalid HTTP method         |\n| `MissingServletRequestParameterException` |     400     |    `web.missing_parameter`    | Name and type of the missing query Param |\n|    `MissingServletRequestPartException`   |     400     |       `web.missing_part`      |         Missing request part name        |\n|         `NoHandlerFoundException`         |     404     |        `web.no_handler`       |             The request path             |\n|      `MissingRequestHeaderException`      |     400     |      `web.missing_header`     |          The missing header name         |\n|      `MissingRequestCookieException`      |     400     |      `web.missing_cookie`     |          The missing cookie name         |\n|      `MissingMatrixVariableException`     |     400     | `web.missing_matrix_variable` |     The missing matrix variable name     |\n|                  `others`                 |     500     |        `unknown_error`        |                     -                    |\n\nAlso, almost all exceptions from the `ResponseStatusException` hierarchy, added in Spring Framework 5+ , are handled compatible\nwith the Spring MVC traditional exceptions.\n\n### Spring Security\nWhen Spring Security is present on the classpath, a `WebErrorHandler` implementation would be responsible to handle\ncommon Spring Security exceptions:\n\n|                   Exception                  | Status Code |         Error Code         |\n|:--------------------------------------------:|:-----------:|:--------------------------:|\n| `AccessDeniedException`                      |     403     | `security.access_denied`   |\n| `AccountExpiredException`                    |     400     | `security.account_expired` |\n| `AuthenticationCredentialsNotFoundException` |     401     | `security.auth_required`   |\n| `AuthenticationServiceException`             |     500     | `security.internal_error`  |\n| `BadCredentialsException`                    |     400     | `security.bad_credentials` |\n| `UsernameNotFoundException`                  |     400     | `security.bad_credentials` |\n| `InsufficientAuthenticationException`        |     401     | `security.auth_required`   |\n| `LockedException`                            |     400     | `security.user_locked`     |\n| `DisabledException`                          |     400     | `security.user_disabled`   |\n| `others`                                     |     500     | `unknown_error`            |\n\n#### Reactive Security\nWhen the Spring Security is detected along with the Reactive stack, the starter registers two extra handlers to handle\nall security related exceptions. In contrast with other handlers which register themselves automatically, in order to use these\ntwo handlers, you should register them in your security configuration manually as follows:\n```java\n@EnableWebFluxSecurity\npublic class WebFluxSecurityConfig {\n\n    // other configurations\n\n    @Bean\n    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,\n                                                            ServerAccessDeniedHandler accessDeniedHandler,\n                                                            ServerAuthenticationEntryPoint authenticationEntryPoint) {\n        http\n                .csrf().accessDeniedHandler(accessDeniedHandler)\n                .and()\n                .exceptionHandling()\n                    .accessDeniedHandler(accessDeniedHandler)\n                    .authenticationEntryPoint(authenticationEntryPoint)\n                // other configurations\n\n        return http.build();\n    }\n}\n```\nThe registered `ServerAccessDeniedHandler` and `ServerAuthenticationEntryPoint` are responsible for handling `AccessDeniedException`\nand `AuthenticationException` exceptions, respectively.\n\n#### Servlet Security\nWhen the Spring Security is detected along with the traditional servlet stack, the starter registers two extra handlers to handle\nall security related exceptions. In contrast with other handlers which register themselves automatically, in order to use these\ntwo handlers, you should register them in your security configuration manually as follows:\n```java\n@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    private final AccessDeniedHandler accessDeniedHandler;\n    private final AuthenticationEntryPoint authenticationEntryPoint;\n\n    public SecurityConfig(AccessDeniedHandler accessDeniedHandler, AuthenticationEntryPoint authenticationEntryPoint) {\n        this.accessDeniedHandler = accessDeniedHandler;\n        this.authenticationEntryPoint = authenticationEntryPoint;\n    }\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        http\n                .exceptionHandling()\n                    .accessDeniedHandler(accessDeniedHandler)\n                    .authenticationEntryPoint(authenticationEntryPoint);\n    }\n}\n```\nThe registered `AccessDeniedHandler` and `AuthenticationEntryPoint` are responsible for handling `AccessDeniedException`\nand `AuthenticationException` exceptions, respectively.\n\n### Error Representation\nBy default, errors would manifest themselves in the HTTP response bodies with the following JSON schema:\n```json\n{\n  \"errors\": [\n    {\n      \"code\": \"the_error_code\",\n      \"message\": \"the_error_message\"\n    }\n  ]\n}\n```\n\n#### Fingerprinting\nThere is also an option to generate error `fingerprint`. Fingerprint is a unique hash of error\nevent which might be used as a correlation ID of error presented to user, and reported in\napplication backend (e.g. in detailed log message). To generate error fingerprints, add\nthe configuration property `errors.add-fingerprint=true`.\n\nWe provide two fingerprint providers implementations:\n - `UuidFingerprintProvider` which generates a random UUID regardless of the handled exception.\n   This is the default provider and will be used out of the box if\n   `errors.add-fingerprint=true` property is configured.\n - `Md5FingerprintProvider` which generates MD5 checksum of full class name of original exception\n   and current time.\n\n#### Customizing the Error Representation\nIn order to change the default error representation, just implement the `HttpErrorAttributesAdapter` \ninterface and register it as *Spring Bean*:\n```java\n@Component\npublic class OopsDrivenHttpErrorAttributesAdapter implements HttpErrorAttributesAdapter {\n    \n    @Override\n    public Map\u003cString, Object\u003e adapt(HttpError httpError) {\n        return Collections.singletonMap(\"Oops!\", httpError.getErrors());\n    }\n}\n```\n\n### Default Error Handler\nBy default, when all registered `WebErrorHandler`s refuse to handle a particular exception, the `LastResortWebErrorHandler`\nwould catch the exception and return a `500 Internal Server Error` with `unknown_error` as the error code.\n\nIf you don't like this behavior, you can change it by registering a *Bean* of type `WebErrorHandler` with\nthe `defaultWebErrorHandler` as the *Bean Name*:\n\n```java\n@Component(\"defaultWebErrorHandler\")\npublic class CustomDefaultWebErrorHandler implements WebErrorHandler {\n    // Omitted\n}\n```\n\n### Refining Exceptions\nSometimes the given exception is not the actual problem and we need to dig deeper to handle the error, say the actual\nexception is hidden as a cause inside the top-level exception. In order to transform some exceptions before handling \nthem, we can register an `ExceptionRefiner` implementation as a *Spring Bean*:\n```java\n@Component\npublic class CustomExceptionRefiner implements ExceptionRefiner {\n    \n    @Override\n    Throwable refine(Throwable exception) {\n        return exception instanceof ConversionFailedException ? exception.getCause() : exception;\n    }\n}\n```\n\n### Logging Exceptions\nBy default, the starter issues a few `debug` logs under the `me.alidg.errors.WebErrorHandlers` logger name.\nIn order to customize the way we log exceptions, we just need to implement the `ExceptionLogger` interface and register it\nas a *Spring Bean*:\n```java\n@Component\npublic class StdErrExceptionLogger implements ExceptionLogger {\n    \n    @Override\n    public void log(Throwable exception) {\n        if (exception != null)\n            System.err.println(\"Failed to process the request: \" + exception);\n    }\n}\n``` \n\n### Post Processing Handled Exceptions\nAs a more powerful alternative to `ExceptionLogger` mechanism, there is also `WebErrorHandlerPostProcessor`\ninterface. You may declare multiple post processors which implement this interface and are exposed\nas *Spring Bean*. Below is an example of more advanced logging post processors:\n```java\n@Component\npublic class LoggingErrorWebErrorHandlerPostProcessor implements WebErrorHandlerPostProcessor {\n    private static final Logger log = LoggerFactory.getLogger(LoggingErrorWebErrorHandlerPostProcessor.class);\n    \n    @Override \n    public void process(@NonNull HttpError error) {\n        if (error.getHttpStatus().is4xxClientError()) {\n            log.warn(\"[{}] {}\", error.getFingerprint(), prepareMessage(error));\n        } else if (error.getHttpStatus().is5xxServerError()) {\n            log.error(\"[{}] {}\", error.getFingerprint(), prepareMessage(error), error.getOriginalException());\n        }\n    }\n    \n    private String prepareMessage(HttpError error) {\n        return error.getErrors().stream()\n                    .map(HttpError.CodedMessage::getMessage)\n                    .collect(Collectors.joining(\"; \"));\n    }\n}\n```\n\n### Registering Custom Handlers\nIn order to provide a custom handler for a specific exception, just implement the `WebErrorHandler` interface for that\nexception and register it as a *Spring Bean*:\n```java\n@Component\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class CustomWebErrorHandler implements WebErrorHandler {\n    \n    @Override\n    public boolean canHandle(Throwable exception) {\n        return exception instanceof ConversionFailedException;\n    }\n\n    @Override\n    public HandledException handle(Throwable exception) {\n        return new HandledException(\"custom_error_code\", HttpStatus.BAD_REQUEST, null);\n    }\n}\n\n```\nIf you're going to register multiple handlers, you can change their priority using `@Order`. Please note that all your custom\nhandlers would be registered after built-in exception handlers (Validation, `ExceptionMapping`, etc.). If you don't like\nthis idea, provide a custom *Bean* of type `WebErrorHandlers` and the default one would be discarded.\n\n### Test Support\nIn order to enable our test support for `WebMvcTest`s, just add the `@AutoConfigureErrors` annotation to your test\nclass. That's how a `WebMvcTest` would look like with errors support enabled:\n```java\n@AutoConfigureErrors\n@RunWith(SpringRunner.class)\n@WebMvcTest(UserController.class)\npublic class UserControllerIT {\n    \n    @Autowired private MockMvc mvc;\n    \n    @Test\n    public void createUser_ShouldReturnBadRequestForInvalidBodies() throws Exception {\n        mvc.perform(post(\"/users\").content(\"{}\"))\n            .andExpect(status().isBadRequest())\n            .andExpect(jsonPath(\"$.errors[0].code\").value(\"username.required\"));    \n    }\n}\n```\nFor `WebFluxTest`s, the test support is almost the same as the Servlet stack:\n```java\n@AutoConfigureErrors\n@RunWith(SpringRunner.class)\n@WebFluxTest(UserController.class)\n@ImportAutoConfiguration(ErrorWebFluxAutoConfiguration.class) // Drop this if you're using Spring Boot 2.1.4+\npublic class UserControllerIT {\n\n    @Autowired private WebTestClient client;\n\n    @Test\n    public void createUser_ShouldReturnBadRequestForInvalidBodies() {\n        client.post()\n                .uri(\"/users\")\n                .syncBody(\"{}\").header(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE)\n                .exchange()\n                .expectStatus().isBadRequest()\n                .expectBody().jsonPath(\"$.errors[0].code\").isEqualTo(\"username.required\");\n    }\n}\n```\n\n## Appendix\n\n### Configuration\nAdditional configuration of this starter can be provided by configuration properties - the Spring Boot way.\nAll configuration properties start with `errors`. Below is a list of supported properties:\n\n|         Property          |             Values             | Default value |\n|:-------------------------:|:------------------------------:|:-------------:|\n| `errors.expose-arguments` | `NEVER`, `NON_EMPTY`, `ALWAYS` |    `NEVER`    |\n| `errors.add-fingerprint`  |        `true`, `false`         |    `false`    |\n\nCheck `ErrorsProperties` implementation for more details.\n\n## License\nCopyright 2018 alimate\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","funding_links":[],"categories":["REST错误处理"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falimate%2Ferrors-spring-boot-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falimate%2Ferrors-spring-boot-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falimate%2Ferrors-spring-boot-starter/lists"}