Ambiguous @ExceptionHandler method mapped for MethodArgumentNotValidException and NoHandlerFoundException

470 views Asked by At

I have a requirement to use the NoHandlerFoundException exception handler within my global exception handler.

I was already using the MethodArgumentNotValidException handler by overriding it from the extended ResponseEntityExceptionHandler class.

Now, while running the application, I am getting this exception.

I was using the method Argument Not valid handler to handle the missing fields inside my request body.

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerExceptionResolver]: Factory method 'handlerExceptionResolver' threw exception with message: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]: {protected org.springframework.http.ResponseEntity com.kroger.iwt.coreservice.exception.handler.GlobalExceptionHandler.handleMethodArgumentNotValid(org.springframework.web.bind.MethodArgumentNotValidException,org.springframework.http.HttpHeaders,org.springframework.http.HttpStatusCode,org.springframework.web.context.request.WebRequest), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest) throws java.lang.Exception}

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ NoHandlerFoundException.class })
    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<ErrorListResponse> notFound(final NoHandlerFoundException exception) {
        log.error("{} {}", 404, exception.getMessage());
        ErrorListResponse errorListResponse = errorResponse("Request URI Not Found", exception);
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorListResponse);
    }

    // @Override
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        List<FieldError> errors = ex.getBindingResult().getFieldErrors();
        List<ErrorListResponse.ErrorDetails> errorDetails = new ArrayList<>();
        for (FieldError fieldError : errors) {
            ErrorListResponse.ErrorDetails error = new ErrorListResponse.ErrorDetails();
            error.setReason(fieldError.getDefaultMessage());
            error.setCode("BAD_REQUEST");
            error.setDatetime(new DateTimeResponse(Instant.now().toString(), "America/New_York"));
            errorDetails.add(error);
        }
        ErrorListResponse errorResponse = new ErrorListResponse();
        errorResponse.setErrors(errorDetails);
        log.error("{} {}", 400, "Multiple fields are invalid");
        return new ResponseEntity(errorResponse, HttpStatus.BAD_REQUEST);
    }
}
2

There are 2 answers

1
Mar-Z On

As you are extending the ResponseEntityExceptionHandler class and override its methods you should not use the @ExceptionHandler annotation.

Otherwise Spring's org.springframework.web.servlet.HandlerExceptionResolver will find more then one handler for the same exception class and report an error.

This is valid also for methods that just handle those exceptions. Even with a different signature and/or in a different class not extending ResponseEntityExceptionHandler. The exceptions are listed here:

@ExceptionHandler({
    HttpRequestMethodNotSupportedException.class,
    HttpMediaTypeNotSupportedException.class,
    HttpMediaTypeNotAcceptableException.class,
    MissingPathVariableException.class,
    MissingServletRequestParameterException.class,
    MissingServletRequestPartException.class,
    ServletRequestBindingException.class,
    MethodArgumentNotValidException.class,
    HandlerMethodValidationException.class,
    NoHandlerFoundException.class,
    NoResourceFoundException.class,
    AsyncRequestTimeoutException.class,
    ErrorResponseException.class,
    MaxUploadSizeExceededException.class,
    ConversionNotSupportedException.class,
    TypeMismatchException.class,
    HttpMessageNotReadableException.class,
    HttpMessageNotWritableException.class,
    MethodValidationException.class,
    BindException.class
})

Solution

Remove @ExceptionHandler annotations from the methods handling the same exceptions as in ResponseEntityExceptionHandler (listed above). For these exceptions you should always override the handler methods and not define your own ones.

0
Nitin Gangwar On

This code worked for me. Just need to use this method definition removing headers, httpStatusCode and making argument as final worked for me. Also I need to remove the inheriting behaviour of ResponseEntityExceptionHandler other wise NohandlerException instantiation will not happen.

@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex) {
    List<FieldError> errors = ex.getBindingResult().getFieldErrors();
    List<ErrorListResponse.ErrorDetails> errorDetails = new ArrayList<>();
    for (FieldError fieldError : errors) {
        ErrorListResponse.ErrorDetails error = new ErrorListResponse.ErrorDetails();
        error.setReason(fieldError.getDefaultMessage());
        error.setCode("BAD_REQUEST");
        error.setDatetime(new DateTimeResponse(Instant.now().toString(), "America/New_York"));
        errorDetails.add(error);
    }
    ErrorListResponse errorResponse = new ErrorListResponse();
    errorResponse.setErrors(errorDetails);
    log.error("{} {}", 400, "Multiple fields are invalid");
    return new ResponseEntity(errorResponse, HttpStatus.BAD_REQUEST);
}


    @ExceptionHandler({ NoHandlerFoundException.class })
public ResponseEntity<ErrorListResponse> notFound(final NoHandlerFoundException exception) {
    log.error("{} {}", 404, exception.getMessage());
    ErrorListResponse errorListResponse = errorResponse("Request URI Not Found", exception);
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorListResponse);
}