Git Product home page Git Product logo

errors-spring-boot-starter's Introduction

Errors Spring Boot Starter

Build Status codecov Maven Central Javadocs Sonatype Sonar Quality Gate License

A Bootiful, Consistent and Opinionated Approach to Handle all sorts of Exceptions.

Table of Contents

Make Error Handling Great Again!

Built on top of Spring Boot's great exception handling mechanism, the errors-spring-boot-starter offers:

  • A consistent approach to handle all exceptions. 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 vs WebExceptionHandler)
  • Built-in support for application specific error codes, again, for all possible errors.
  • Simple error message interpolation using plain old MessageSources.
  • Customizable HTTP error representation.
  • Exposing arguments from exceptions to error messages.
  • Supporting both traditional and reactive stacks.
  • Customizable exception logging.
  • Supporting error fingerprinting.

Getting Started

Download

Download the latest JAR or grab via Maven:

<dependency>
    <groupId>me.alidg</groupId>
    <artifactId>errors-spring-boot-starter</artifactId>
    <version>1.4.0</version>
</dependency>

or Gradle:

compile "me.alidg:errors-spring-boot-starter:1.4.0"

If you like to stay at the cutting edge, use our 1.5.0-SNAPSHOT version. Of course you should define the following snapshot repository:

<repositories>
    <repository>
        <id>Sonatype</id>
        <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
    </repository>
</repositories>

or:

repositories {
    maven {
      url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
}

Prerequisites

The main dependency is JDK 8+. Tested with:

  • JDK 8, JDK 9, JDK 10 and JDK 11 on Linux.
  • Spring Boot 2.2.0.RELEASE (Also, should work with any 2.0.0+)

Overview

The WebErrorHandler implementations are responsible for handling different kinds of exceptions. When an exception happens, the WebErrorHandlers (A factory over all WebErrorHandler implementations) catches the exception and would find an appropriate implementation to handle the exception. By default, WebErrorHandlers consults with the following implementations to handle a particular exception:

  • 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.

After delegating to the appropriate handler, the WebErrorHandlers turns the handled exception result into a HttpError, which encapsulates the HTTP status code and all error code/message combinations.

Error Codes

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.

In errors-spring-boot-starter, one can map exceptions to error codes in different ways:

  • Validation error codes can be extracted from the Bean Validation's constraints:

    public class User {  
    
        @NotBlank(message = "username.required")
        private final String username;
     
        @NotBlank(message = "password.required")
        @Size(min = 6, message = "password.min_length")
        private final String password;
     
        // constructor and getter and setters
    }

    To report a violation in password length, the password.min_length would be reported as the error code. As you may guess, one validation exception can contain multiple error codes to report all validation violations at once.

  • Specifying the error code for custom exceptions using the @ExceptionMapping annotation:

    @ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
    public class UserAlreadyExistsException extends RuntimeException {}

    The UserAlreadyExistsException exception would be mapped to user.already_exists error code.

  • Specifying the error code in a WebErrorHandler implementation:

    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);
        }
    }

Error Message

Once the exception mapped to error code(s), we can add a companion and Human Readable error message. This can be done by registering a Spring MessageSource to perform the code-to-message translation. For example, if we add the following key-value pair in our message resource file:

user.already_exists=Another user with the same username already exists

Then if an exception of type UserAlreadyExistsException was thrown, you would see a 400 Bad Request HTTP response with a body like:

{
  "errors": [
    {
      "code": "user.already_exists",
      "message": "Another user with the same username already exists"
    }
  ]
}

Since MessageSource supports Internationalization (i18n), our error messages can possibly have different values based on each Locale.

Exposing Arguments

With Bean Validation you can pass parameters from the constraint validation, e.g. @Size, to its corresponding interpolated message. For example, if we have:

password.min_length=The password must be at least {0} characters

And a configuration like:

@Size(min = 6, message = "password.min_length")
private final String password;

The min attribute from the @Size constraint would be passed to the message interpolation mechanism, so:

{
  "errors": [
    {
      "code": "password.min_length",
      "message": "The password must be at least 6 characters"
    }
  ]
}

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 message:

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

We could write:

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

Then the username property from the UserAlreadyExistsException would be available to the message under the user.already_exists key as the first argument. @ExposeAsArg can be used on fields and no-arg methods with a return type. The HandledException class also accepts the to-be-exposed arguments in its constructor.

Exposing Named Arguments

By default error arguments will be used in message interpolation only. It is also possible to additionally get those arguments in error response by defining the configuration property errors.expose-arguments. When enabled, you might get the following response payload:

{
  "errors": [
    {
      "code": "password.min_length",
      "message": "The password must be at least 6 characters",
      "arguments": {
        "min": 6
      }
    }
  ]
}

The errors.expose-arguments property takes 3 possible values:

  • NEVER - named arguments will never be exposed. This is the default setting.
  • NON_EMPTY - named arguments will be exposed only in case there are any. If error has no arguments, result payload will not have "arguments" element.
  • ALWAYS - the "arguments" element is always present in payload, even when the error has no arguments. In that case empty map will be provided: "arguments": {}.

Checkout here for more detail on how we expose arguments for different exception categories.

Named Arguments Interpolation

You can use either positional or named argument placeholders in message templates. Given:

@Size(min = 6, max = 20, message = "password.length")
private final String password;

You can create message template in messages.properties with positional arguments:

password.length=Password must have length between {1} and {0}

Arguments are sorted by name. Since lexicographically max < min, placeholder {0} will be substituted with argument max, and {1} will have value of argument min.

You can also use argument names as placeholders:

password.length=Password must have length between {min} and {max}

Named arguments interpolation works out of the box, regardless of the errors.expose-arguments value. You can mix both approaches, but it is not recommended.

If there is a value in the message that should not be interpolated, escape the first { character with a backslash:

password.length=Password \\{min} is {min} and \\{max} is {max}

After interpolation, this message would read: Password {min} is 6 and {max} is 20.

Arguments annotated with @ExposeAsArg will be named by annotated field or method name:

@ExposeAsArg(0)
private final String argName; // will be exposed as "argName"

This can be changed by the name parameter:

@ExposeAsArg(value = 0, name = "customName")
private final String argName; // will be exposed as "customName"

Validation and Binding Errors

Validation errors can be processed as you might expect. For example, if a client passed an empty JSON to a controller method like:

@PostMapping
public void createUser(@RequestBody @Valid User user) {
    // omitted
}

Then the following error would be returned:

{
  "errors": [
    {
      "code": "password.min_length",
      "message": "corresponding message!"
    },
    {
       "code": "password.required",
       "message": "corresponding message!"
    },
    {
      "code": "username.required",
      "message": "corresponding message!"
    }
  ]
}

Bean Validation's ConstraintViolationExceptions will be handled in the same way, too.

Custom Exceptions

Custom exceptions can be mapped to status code and error code combination using the @ExceptionMapping annotation:

@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {}

Here, every time we catch an instance of UserAlreadyExistsException, a Bad Request HTTP response with user.already_exists error would be returned.

Also, it's possible to expose some arguments from custom exceptions to error messages using the ExposeAsArg:

@ExceptionMapping(statusCode = BAD_REQUEST, errorCode = "user.already_exists")
public class UserAlreadyExistsException extends RuntimeException {
    @ExposeAsArg(0) private final String username;
    
    // constructor
    
    @ExposeAsArg(1)
    public String exposeThisToo() {
        return "42";
    }
}

Then the error message template can be something like:

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

During message interpolation, the {0} and {1} placeholders would be replaced with annotated field's value and method's return value. The ExposeAsArg annotation is applicable to:

  • Fields
  • No-arg methods with a return type

Spring MVC

By default, a custom WebErrorHandler is registered to handle common exceptions thrown by Spring MVC:

Exception Status Code Error Code Exposed Args
HttpMessageNotReadableException 400 web.invalid_or_missing_body -
HttpMediaTypeNotAcceptableException 406 web.not_acceptable List of acceptable MIME types
HttpMediaTypeNotSupportedException 415 web.unsupported_media_type The unsupported content type
HttpRequestMethodNotSupportedException 405 web.method_not_allowed The invalid HTTP method
MissingServletRequestParameterException 400 web.missing_parameter Name and type of the missing query Param
MissingServletRequestPartException 400 web.missing_part Missing request part name
NoHandlerFoundException 404 web.no_handler The request path
MissingRequestHeaderException 400 web.missing_header The missing header name
MissingRequestCookieException 400 web.missing_cookie The missing cookie name
MissingMatrixVariableException 400 web.missing_matrix_variable The missing matrix variable name
others 500 unknown_error -

Also, almost all exceptions from the ResponseStatusException hierarchy, added in Spring Framework 5+ , are handled compatible with the Spring MVC traditional exceptions.

Spring Security

When Spring Security is present on the classpath, a WebErrorHandler implementation would be responsible to handle common Spring Security exceptions:

Exception Status Code Error Code
AccessDeniedException 403 security.access_denied
AccountExpiredException 400 security.account_expired
AuthenticationCredentialsNotFoundException 401 security.auth_required
AuthenticationServiceException 500 security.internal_error
BadCredentialsException 400 security.bad_credentials
UsernameNotFoundException 400 security.bad_credentials
InsufficientAuthenticationException 401 security.auth_required
LockedException 400 security.user_locked
DisabledException 400 security.user_disabled
others 500 unknown_error

Reactive Security

When the Spring Security is detected along with the Reactive stack, the starter registers two extra handlers to handle all security related exceptions. In contrast with other handlers which register themselves automatically, in order to use these two handlers, you should register them in your security configuration manually as follows:

@EnableWebFluxSecurity
public class WebFluxSecurityConfig {

    // other configurations

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
                                                            ServerAccessDeniedHandler accessDeniedHandler,
                                                            ServerAuthenticationEntryPoint authenticationEntryPoint) {
        http
                .csrf().accessDeniedHandler(accessDeniedHandler)
                .and()
                .exceptionHandling()
                    .accessDeniedHandler(accessDeniedHandler)
                    .authenticationEntryPoint(authenticationEntryPoint)
                // other configurations

        return http.build();
    }
}

The registered ServerAccessDeniedHandler and ServerAuthenticationEntryPoint are responsible for handling AccessDeniedException and AuthenticationException exceptions, respectively.

Servlet Security

When the Spring Security is detected along with the traditional servlet stack, the starter registers two extra handlers to handle all security related exceptions. In contrast with other handlers which register themselves automatically, in order to use these two handlers, you should register them in your security configuration manually as follows:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final AccessDeniedHandler accessDeniedHandler;
    private final AuthenticationEntryPoint authenticationEntryPoint;

    public SecurityConfig(AccessDeniedHandler accessDeniedHandler, AuthenticationEntryPoint authenticationEntryPoint) {
        this.accessDeniedHandler = accessDeniedHandler;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .exceptionHandling()
                    .accessDeniedHandler(accessDeniedHandler)
                    .authenticationEntryPoint(authenticationEntryPoint);
    }
}

The registered AccessDeniedHandler and AuthenticationEntryPoint are responsible for handling AccessDeniedException and AuthenticationException exceptions, respectively.

Error Representation

By default, errors would manifest themselves in the HTTP response bodies with the following JSON schema:

{
  "errors": [
    {
      "code": "the_error_code",
      "message": "the_error_message"
    }
  ]
}

Fingerprinting

There is also an option to generate error fingerprint. Fingerprint is a unique hash of error event which might be used as a correlation ID of error presented to user, and reported in application backend (e.g. in detailed log message). To generate error fingerprints, add the configuration property errors.add-fingerprint=true.

We provide two fingerprint providers implementations:

  • UuidFingerprintProvider which generates a random UUID regardless of the handled exception. This is the default provider and will be used out of the box if errors.add-fingerprint=true property is configured.
  • Md5FingerprintProvider which generates MD5 checksum of full class name of original exception and current time.

Customizing the Error Representation

In order to change the default error representation, just implement the HttpErrorAttributesAdapter interface and register it as Spring Bean:

@Component
public class OopsDrivenHttpErrorAttributesAdapter implements HttpErrorAttributesAdapter {
    
    @Override
    public Map<String, Object> adapt(HttpError httpError) {
        return Collections.singletonMap("Oops!", httpError.getErrors());
    }
}

Default Error Handler

By default, when all registered WebErrorHandlers refuse to handle a particular exception, the LastResortWebErrorHandler would catch the exception and return a 500 Internal Server Error with unknown_error as the error code.

If you don't like this behavior, you can change it by registering a Bean of type WebErrorHandler with the defaultWebErrorHandler as the Bean Name:

@Component("defaultWebErrorHandler")
public class CustomDefaultWebErrorHandler implements WebErrorHandler {
    // Omitted
}

Refining Exceptions

Sometimes the given exception is not the actual problem and we need to dig deeper to handle the error, say the actual exception is hidden as a cause inside the top-level exception. In order to transform some exceptions before handling them, we can register an ExceptionRefiner implementation as a Spring Bean:

@Component
public class CustomExceptionRefiner implements ExceptionRefiner {
    
    @Override
    Throwable refine(Throwable exception) {
        return exception instanceof ConversionFailedException ? exception.getCause() : exception;
    }
}

Logging Exceptions

By default, the starter issues a few debug logs under the me.alidg.errors.WebErrorHandlers logger name. In order to customize the way we log exceptions, we just need to implement the ExceptionLogger interface and register it as a Spring Bean:

@Component
public class StdErrExceptionLogger implements ExceptionLogger {
    
    @Override
    public void log(Throwable exception) {
        if (exception != null)
            System.err.println("Failed to process the request: " + exception);
    }
}

Post Processing Handled Exceptions

As a more powerful alternative to ExceptionLogger mechanism, there is also WebErrorHandlerPostProcessor interface. You may declare multiple post processors which implement this interface and are exposed as Spring Bean. Below is an example of more advanced logging post processors:

@Component
public class LoggingErrorWebErrorHandlerPostProcessor implements WebErrorHandlerPostProcessor {
    private static final Logger log = LoggerFactory.getLogger(LoggingErrorWebErrorHandlerPostProcessor.class);
    
    @Override 
    public void process(@NonNull HttpError error) {
        if (error.getHttpStatus().is4xxClientError()) {
            log.warn("[{}] {}", error.getFingerprint(), prepareMessage(error));
        } else if (error.getHttpStatus().is5xxServerError()) {
            log.error("[{}] {}", error.getFingerprint(), prepareMessage(error), error.getOriginalException());
        }
    }
    
    private String prepareMessage(HttpError error) {
        return error.getErrors().stream()
                    .map(HttpError.CodedMessage::getMessage)
                    .collect(Collectors.joining("; "));
    }
}

Registering Custom Handlers

In order to provide a custom handler for a specific exception, just implement the WebErrorHandler interface for that exception and register it as a Spring Bean:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomWebErrorHandler implements WebErrorHandler {
    
    @Override
    public boolean canHandle(Throwable exception) {
        return exception instanceof ConversionFailedException;
    }

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

If you're going to register multiple handlers, you can change their priority using @Order. Please note that all your custom handlers would be registered after built-in exception handlers (Validation, ExceptionMapping, etc.). If you don't like this idea, provide a custom Bean of type WebErrorHandlers and the default one would be discarded.

Test Support

In order to enable our test support for WebMvcTests, just add the @AutoConfigureErrors annotation to your test class. That's how a WebMvcTest would look like with errors support enabled:

@AutoConfigureErrors
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerIT {
    
    @Autowired private MockMvc mvc;
    
    @Test
    public void createUser_ShouldReturnBadRequestForInvalidBodies() throws Exception {
        mvc.perform(post("/users").content("{}"))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.errors[0].code").value("username.required"));    
    }
}

For WebFluxTests, the test support is almost the same as the Servlet stack:

@AutoConfigureErrors
@RunWith(SpringRunner.class)
@WebFluxTest(UserController.class)
@ImportAutoConfiguration(ErrorWebFluxAutoConfiguration.class) // Drop this if you're using Spring Boot 2.1.4+
public class UserControllerIT {

    @Autowired private WebTestClient client;

    @Test
    public void createUser_ShouldReturnBadRequestForInvalidBodies() {
        client.post()
                .uri("/users")
                .syncBody("{}").header(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE)
                .exchange()
                .expectStatus().isBadRequest()
                .expectBody().jsonPath("$.errors[0].code").isEqualTo("username.required");
    }
}

Appendix

Configuration

Additional configuration of this starter can be provided by configuration properties - the Spring Boot way. All configuration properties start with errors. Below is a list of supported properties:

Property Values Default value
errors.expose-arguments NEVER, NON_EMPTY, ALWAYS NEVER
errors.add-fingerprint true, false false

Check ErrorsProperties implementation for more details.

License

Copyright 2018 alimate

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

errors-spring-boot-starter's People

Contributors

alimate avatar mona-mohamadinia avatar nmlynch94 avatar zarebski-m avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

errors-spring-boot-starter's Issues

FileNotFoundException with Spring Boot 2.0.2.RELEASE

I'm using spring boot 2.0.2.RELEASE, when maven wants to build the project, I got this exception:

	at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:245) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getOrder(AutoConfigurationSorter.java:214) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.access$000(AutoConfigurationSorter.java:155) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.autoconfigure.AutoConfigurationSorter.lambda$getInPriorityOrder$0(AutoConfigurationSorter.java:62) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:1.8.0_102]
	at java.util.TimSort.sort(TimSort.java:234) ~[na:1.8.0_102]
	at java.util.Arrays.sort(Arrays.java:1512) ~[na:1.8.0_102]
	at java.util.ArrayList.sort(ArrayList.java:1454) ~[na:1.8.0_102]
	at org.springframework.boot.autoconfigure.AutoConfigurationSorter.getInPriorityOrder(AutoConfigurationSorter.java:61) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup.sortAutoConfigurations(AutoConfigurationImportSelector.java:408) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup.selectImports(AutoConfigurationImportSelector.java:394) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGrouping.getImports(ConfigurationClassParser.java:831) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:563) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:188) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:139) [spring-boot-test-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) [spring-test-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:365) [surefire-junit4-2.21.0.jar:2.21.0]
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:273) [surefire-junit4-2.21.0.jar:2.21.0]
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:238) [surefire-junit4-2.21.0.jar:2.21.0]
	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:159) [surefire-junit4-2.21.0.jar:2.21.0]
	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:379) [surefire-booter-2.21.0.jar:2.21.0]
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:340) [surefire-booter-2.21.0.jar:2.21.0]
	at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125) [surefire-booter-2.21.0.jar:2.21.0]
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:413) [surefire-booter-2.21.0.jar:2.21.0]
Caused by: java.io.FileNotFoundException: class path resource [ me/alidg/errors/conf/ServletSecurityErrorsAutoConfiguration.class] cannot be opened because it does not exist
	at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:180) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:51) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.createMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:88) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.getMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:75) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:81) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.boot.autoconfigure.AutoConfigurationSorter$AutoConfigurationClass.getAnnotationMetadata(AutoConfigurationSorter.java:241) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	... 52 common frames omitted```

Add support to pick fields of the Exception class

Usually, we put valuable data inside the exceptions like traceId, the requested URI, the rejected or failed input values and...
That will be awesome if you add an option to pick the fields of the Exception class and add them to the error representation JSON. For now, It just ignores all fields of the Exception class.

The Exception class:

@ExceptionMapping(statusCode = HttpStatus.NOT_FOUND, errorCode = "the_error_code")
@NoArgsConstructor
@AllArgsConstructor
public class NotFoundException extends Exception {
    private String traceId;
}

What I expect:

{
  "errors": [
    {
      "code": "the_error_code",
      "message": "the_error_message",
      "traceId": "some data here or null if not set"
    }
  ]
}

What I get:

{
  "errors": [
    {
      "code": "the_error_code",
      "message": "the_error_message"
    }
  ]
}

Validation errors default behavior

Hi,

IMHO the default behavior regarding validation errors can be improved. Indeed, currently it seems that this lib doesn't rely on the default message provided by the validation api.
Furthermore, the in error field isn't in the resulting json.

For example :

{
    "errors": [
        {
            "code": "javax.validation.constraints.Size.message",
            "message": null
        }
    ]
}

To me, if there is no customization, the json should contain the field and the default error message provided by the validation api.

What do you think about that ?

Have a nice day :)

Consider Adding an Authentication Entry Point

Synopsis

When the anonymous is disabled on Spring Security, The ExceptionTranslationFilter would delegate the AuthenticationExceptions to an AuthenticationEntryPoint, so the WebErrorHandlers would not get a chance to handle the exception.

Possible Solution

Maybe the solution is as simple as registering a custom AuthenticationEntryPoint.

Using @SpringBootTests to Bootstrap Integration Tests

Synopsis

Currently, in order to bootstrap the integration tests, we're creating the appropriate ApplicationContext instance manually which has proven to be cumbersome and error-prone.

Proposal

We can simply use the @SpringBootTest and bootstrap a complete Spring Boot application before running tests.

Handling Type Mismatches

Synopsis

Suppose:

@RestController
@RequestMapping("/somewhere")
public class TheController {
     
    @GetMapping
    public String handle(@RequestParam Integer page) {
        // Return something
    }
}

Then if we a send a request to somewhere like /somewhere?page=invalid, Then Spring MVC throws an instance of TypeMismatchException or MethodArgumentTypeMismatchException which we don't handle properly.

Exposing Metrics

Synopsis

Since we catch all possible exceptions and know about their details, we might as well expose some metrics about them, too.

Possible Solution

We may register a WebErrorHandlerPostProcessor conditionally to expose the related metrics about the handled exception, its corresponding error code, status code, etc.

Allow overriding included implementations

It should be possible to override the built in error handlers with a custom solution if needed. Because of not being able to do this using this library as is won't work because of #20.

Otherwise, the built-in handlers should be more specific on which exceptions they can handle.

Handle General MultipartExceptions

Synopsis

Sometimes Spring MVC and WebFlux decide to throw MultipartException for multipart requests. For example, if the endpoint supposed to be a multipart one but the request isn't, then Spring throws this general exception.

Drop the Registered Validator, if Possible

Synopsis

Since we changed our strategy regarding the validation exceptions, we may do not need to register an instance of Validator in our auto-configuration.

Steps

  • Investigate the Possibility
  • Drop the Validator
  • Drop the Post Processor.

Refine Exceptions

Synopsis

Sometimes the caught exception is just a symptom, not the cause of the failure. The WebErrorHandlers should be able to refine a particular exception before starting the handling procedure.

Adding Support for Spring MVC

Steps

  • Providing a ControllerAdvice to catch all exceptions and turn them into meaningful code-message paris using the WebErrorHandlers factory.
  • Providing a custom ErrorController to catch the remaining exceptions.
  • A WebErrorHandler implementation for Spring MVC specific exceptions

Adding Support for Spring 5.1 Servlet Exceptions

Spring Framework 5.1 added a few new Servlet related exceptions to report missing Header, Cookie or Matrix Variables:

  • MissingMatrixVariableException
  • MissingRequestCookieException
  • MissingRequestHeaderException

We should consider adding another built-in handler to handle these exceptions in a backward compatible way.

Investigating the Possibility of Supporting Multiple Refiners

Synopsis

Currently, one can only register at most one ExceptionRefiner instance to refine exceptions before processing them. That might be useful to extend this feature in a way that it supports multiple refiners.

Possible Use Cases

It's a common pattern to wrap some exceptions into other exceptions which are more appropriate to a particular abstraction level. For example, when Jackson fails to de-serialize a byte-array into a POJO, the thrown exception will be wrapped inside a Spring exception. The only way for us to handle those Jackson exceptions is to:

  1. Provide a built-in refiner to translate the Spring exception into the Jackson exception.
  2. And then handle that Jackson exception using another built-in WebErrorHandler.

No error handling

Hi! Great project!

Added in the dependency and getting null when trying simple 404 test. I must be doing something very wrong somewhere any ideas?

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

null

There was an unexpected error (type=null, status=null)

Leaking Exception Details for BindExceptions

Synopsis

Currently, the SpringValidationWebErrorHandler is responsible for handling all BindExceptions. For validation related errors this works just fine but for other binding failures such as missing fields or type mismatches the current implementation exposes the exception details as the error code, hence leaking too much information!

Steps to Reproduce

  1. Create a controller like:
@RestController
@RequestMapping("/persons")
public class PersonController {

    @GetMapping
    public List<Person> filterPersons(Filter filter) {
        // Return something
    }

    public static class Filter {
        private Type type;

        // Getters and setters 
    }

    public enum Type {
        INTROVERT, EXTRPVERT
    }
}
  1. Then if we send a request to /persons?type=invalid, the error would be something like:
{
  "errors": [
    {
      "code": "Failed to convert property value of type 'java.lang.String' to required type 'me.alidg.reactiveweb.PersonController$Type' for property 'type'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [me.alidg.reactiveweb.PersonController$Type] for value 'invalid'; nested exception is java.lang.IllegalArgumentException: No enum constant me.alidg.reactiveweb.PersonController.Type.invalid",
      "message": null
    }
  ]
}

That's too much information!

Possible Solutions

  • Change the error code extraction in the SpringValidationWebErrorHandler to not leak the exception details.
  • The SpringValidationWebErrorHandler should not handle BindingExceptions that are originating from type mismatchtes or missing fields.
  • Maybe a BindingErrorProcessor implementation could do the trick.

Modularizing the Project

Synopsis

Currently, we only support the 2.x.x versions of Spring Boot. Supporting multiple versions of Spring Boot in a single codebase seems complicated, since each major release may introduce conflicting and backward-incompatible changes.

Motivation

With a few simple changes, we should be able to support the 1.5.x but I couldn't find a way to pull this off, yet! Also, supporting future versions may get as complicated.

Recommended Solution

One solution is to modularize (not to be confused with JPMS) the project. That is, we can create:

  • A Core Module containing the basic and version agnostic abstractions like WebErrorHandler, FingerPrintProvider or HttpError.
  • One dedicated module for each Spring Boot version, e.g. 1.x.x, 2.x.x, etc.

Encapsulate more information into HttpError

Synopsis

In order to add more and more details in error HTTP responses, we may need to enrich the HttpError class with more relevant information such as the WebRequest (See #32).

Possible Solutions

  • One approach is to enhance the HttpErrorAttributesAdaptor as @bfkelsey did in his great PR (#32)
  • The other option is to enrich the HttpError in a backward compatible way.
  • We should all possible web stacks, i.e. Servlet and Reactive.

Add support for required headers

Currently a 500 will be returned if a header that is required is missing. Standard Spring will return a 400 which should be the default behavior of this tool.

I did a little searching and it seems Spring just uses a ServletRequestBindingException for missing headers. This doesn't provide any way to get the header name or type so it would be a little hard to change the message. The default message is something like: "Missing request header 'name' for method parameter of type String"

Interpolation with Message Expressions

Synopsis

Currently, we only support positional arguments in message interpolations, as follows:

age.min=You should be at least {1} years old but you're {0} years old!

As we expose more and more arguments, those {0}, {1}, etc. arguments would make less sense.

Message Expressions

We can simply support message expressions like:

age.min=You should be at least {value} years old but you're {invalid} years old!

Since after the merge of #46, all arguments would have names, then we already have the required infrastructure.

Possible Solution

We may use MessageSource along with Spring's EL or Expression Language.

Supporting Spring Boot 1.5.x

Since we're not too dependent on Spring Boot 2.x abstractions, it may be possible to add the 1.5.x version support.

@SpringBootTest and @AutoConfigureMockMvc errors not mocked

Errors are not mocked in this code:

@AutoConfigureErrors
@SpringBootTest(classes = Application.class)
@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
@AutoConfigureMockMvc
public class MockBaseTests extends AbstractTestNGSpringContextTests {

    protected static final Logger logger = LoggerFactory.getLogger(MockBaseTests.class);

    @Autowired
    protected MockMvc mockMvc;

    @Test
    public void loadContexts() {
        logger.info("Mock base tests is loading");
    }

    @Test
    public void testNotFound() throws Exception {
//no path /404       
mockMvc.perform(get("/404").contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isNotFound())
               .andDo(print());
    }

}

Supporting Spring Boot 2.2

As the Spring Framework 5.2 and Spring Boot 2.2 are both around the corner, we might as well start the integration process before the actual release.

Provide support for using Kotlin

When using Kotlin missing paramters can be thrown as MissingKotlinParameterException or MismatchedInputException. It would be nice to have a default handler for these errors.

Handle MaxUploadSizeExceededException Exception

Synopsis

When uploading a file, if the given file is bigger than the maximum possible size, it would throw a MaxUploadSizeExceededException exception.

Requirements

  • Appropriate Error Code
  • Expose Maximum Size as an Argument

Duplicate Key exception thrown when two of the same error is reported inside a list

Firstly, thank you for all the work you've done on this library. It's a pleasure to use.

If I have the following classes:

public class Foo {
    @NotNull(message = "a.required")
    private int a;
    @NotNull(message = "b.required")
    private int b;

    ... getters/setters
}

And

public class Bar {
    @Valid
    private List<Foo> foos;

    ... getters/setters   
}

If I validate Bar which has a list of Foo objects it seems to work fine as long as the same error isn't thrown twice. So if I have two Foo objects, one with a null 'a' field and one with a null 'b' field it works fine. If I have a list of Foo objects where two or more have null 'b' objects, for example, I get the following:

2018-11-26 18:01:26.763  WARN 18876 --- [o-11000-exec-10] .m.m.a.ExceptionHandlerExceptionResolver : Failed to invoke @ExceptionHandler method: public org.springframework.http.ResponseEntity<?> me.alidg.er
rors.mvc.ErrorsControllerAdvice.handleException(java.lang.Throwable,java.util.Locale)

java.lang.IllegalStateException: Duplicate key []
        at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) ~[na:1.8.0_161]
        at java.util.HashMap.merge(HashMap.java:1254) ~[na:1.8.0_161]
        at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320) ~[na:1.8.0_161]
        at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) ~[na:1.8.0_161]
        at java.util.LinkedList$LLSpliterator.forEachRemaining(LinkedList.java:1235) ~[na:1.8.0_161]
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_161]
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_161]
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_161]
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_161]
        at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_161]
        at me.alidg.errors.handlers.SpringValidationWebErrorHandler.handle(SpringValidationWebErrorHandler.java:56) ~[errors-spring-boot-starter-1.2.0.jar:na]
        at me.alidg.errors.WebErrorHandlers.handle(WebErrorHandlers.java:120) ~[errors-spring-boot-starter-1.2.0.jar:na]
        at me.alidg.errors.mvc.ErrorsControllerAdvice.handleException(ErrorsControllerAdvice.java:58) ~[errors-spring-boot-starter-1.2.0.jar:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:405) ~[spring-webmvc-5.0.4.RELEASE.
jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver.doResolveException(AbstractHandlerMethodExceptionResolver.java:61) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:140) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:78) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1255) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1062) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:881) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855) [spring-webmvc-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-embed-websocket-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:158) [spring-boot-actuator-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:126) [spring-boot-actuator-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:111) [spring-boot-actuator-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:84) [spring-boot-actuator-2.0.0.RELEASE.jar:2.0.0.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_161]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_161]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.28.jar:8.5.28]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]

It seems not to like the duplicate error codes. Is there a supported way around this or is something custom needed? I'd like it to just continue and return the error once instead of throwing an exception.

Document Exposed Arguments

Synopsis

Currently reading the source code is the only way to discover the exposed arguments and their names for a particular exception. We have to document all exposed arguments per exception.

Run Tests in Multiple Environments

Synopsis

Currently, our tests are running only on JDK 8 and Linux. In order to make sure it's gonna work everywhere, we should run tests with multiple OSes and multiple JDK versions.

Steps

  • Support OSx
  • Support Linux
  • JDK 8
  • JDK 9
  • JDK 10

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.