79

I'm trying to handle MethodArgumentNotValidException using @ControllerAdvice as code given below:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    
    private Logger log = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @ExceptionHandler({ ConstraintViolationException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorWrapper handleConstraintViolationException(ConstraintViolationException e) {
        String fieldName = e.getConstraintName();
        String message = getResourceMessage(fieldName + ".alreadyExists", "Already Exists");
        return new ErrorWrapper(fieldName + ".error", message);
    }
    
    @ExceptionHandler({ MethodArgumentNotValidException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorWrapper handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        return new ErrorWrapper(".error", "test");
    }
    
    private String getResourceMessage(String key, String defaultMessage) {
        String message = applicationContext.getMessage(key, null, Locale.getDefault());
        if (StringUtils.isNotEmpty(message)) {
            return message;
        }
        return defaultMessage;
    }
}

I'm getting following Exception

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]: {public com.ca.bean.ErrorWrapper com.ca.exceptionHandler.RestResponseEntityExceptionHandler.handleMethodArgumentNotValidException(org.springframework.web.bind.MethodArgumentNotValidException), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest)}
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1578) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:775) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861) ~[spring-context-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) ~[spring-context-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:444) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:326) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) [spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5099) [catalina.jar:7.0.70]
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5615) [catalina.jar:7.0.70]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147) [catalina.jar:7.0.70]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1571) [catalina.jar:7.0.70]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1561) [catalina.jar:7.0.70]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_92]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_92]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_92]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_92]
Caused by: java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]: {public com.ca.bean.ErrorWrapper com.ca.exceptionHandler.RestResponseEntityExceptionHandler.handleMethodArgumentNotValidException(org.springframework.web.bind.MethodArgumentNotValidException), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest)}
    at org.springframework.web.method.annotation.ExceptionHandlerMethodResolver.addExceptionMapping(ExceptionHandlerMethodResolver.java:109) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.method.annotation.ExceptionHandlerMethodResolver.<init>(ExceptionHandlerMethodResolver.java:76) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.initExceptionHandlerAdviceCache(ExceptionHandlerExceptionResolver.java:265) ~[spring-webmvc-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.afterPropertiesSet(ExceptionHandlerExceptionResolver.java:241) ~[spring-webmvc-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    ... 21 common frames omitted

I'm using spring 4.3.0. I want to handle MethodArgumentNotValidException and want to send custom message. What mistake I'm doing? Please help.

Kiwoth
  • 11
  • 4
Krishna
  • 1,865
  • 4
  • 18
  • 26
  • https://howtodoinjava.com/spring-core/spring-exceptionhandler-annotation/ might be useful for others – Akki Sep 23 '19 at 11:12
  • Hello fixed the problem I hope it helps you https://stackoverflow.com/a/74552716/14538782 – smhylc Nov 23 '22 at 20:54
  • 1
    just remove the `extends ResponseEntityExceptionHandler` from your class, you won't have the Ambiguous @ExceptionHandler issue anymore. – tonnoz Feb 22 '23 at 10:47

8 Answers8

138

Springs ResponseEntityExceptionHandler has a method handleException which is annotated with :

@ExceptionHandler({     
        ...
        MethodArgumentNotValidException.class,
        ... 
    })

Your method handleMethodArgumentNotValidException is also annotated to handle MethodArgumentNotValidException. So spring finds two methods that should be used to handle the same exception, that is the reason for the exception.

**Solution ** Do not add a new method handleMethodArgumentNotValidException instead just override the method ResponseEntityExceptionHandler.handleMethodArgumentNotValid , and do not annotate it. Your class ErrorWrapper must extend ResponseEntity for that.

chrismacp
  • 3,834
  • 1
  • 30
  • 37
  • 13
    handleException method is final method. Hence we cant override that. But that method is having instanceof check for MethodArgumentNotValidException and invoking method with name handleMethodArgumentNotValid. So i tried overriding handleMethodArgumentNotValid method and it worked. Thanks a lot. – Krishna Jul 09 '16 at 16:23
  • 1
    @Krishna is it possible to show how you solved this ? – Dimitri Kopriwa Nov 10 '16 at 17:28
  • @BigDong...I need to see code which i dont have right now. Ill check and give code but it will take 9-10hours – Krishna Nov 11 '16 at 07:02
  • @BigDong..In such situation you can catch parent exception and inside you can do instance of check and proceed – Krishna Nov 11 '16 at 17:15
  • And? Is there @Order(9000) solution? – Daniil Iaitskov Sep 13 '18 at 18:34
  • Oh I see. The configuration comes from parent class which is abstract. Didn't expect that. So without the advice all this "defaults mappers" are not working. – Daniil Iaitskov Sep 13 '18 at 18:39
  • The best part of this solution is the 2nd last line: Override the method, but do NOT annotate it with @ExceptionHandler. Pointing this out for beginners. – Ensei_Tankado Oct 30 '22 at 07:57
20

Handel Exception

@RestControllerAdvice
@Slf4j
public class ExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        List<String> errorList = ex
                .getBindingResult()
                .getFieldErrors()
                .stream()
                .map(fieldError -> fieldError.getDefaultMessage())
                .collect(Collectors.toList());
        ErrorDetails errorDetails = new ErrorDetails(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errorList);
        return handleExceptionInternal(ex, errorDetails, headers, errorDetails.getStatus(), request);
    }
}

Customize Error Message

@Data
public class ErrorDetails {
    private HttpStatus status;
    private String message;
    private List<String> errors;

    public ErrorDetails(HttpStatus status, String message, List<String> errors) {
        super();
        this.status = status;
        this.message = message;
        this.errors = errors;
    }

    public ErrorDetails(HttpStatus status, String message, String error) {
        super();
        this.status = status;
        this.message = message;
        errors = Arrays.asList(error);
    }
}

Sample Response

  {
      "status": "BAD_REQUEST",
      "message": "Validation failed for argument [0] in public void com.ns.hospitalmanagement.resource.PatientResource.createPatient(com.ns.hospitalmanagement.entity.Patient) with 2 errors: [Field error in object 'patient' on field 'name': rejected value [null]; codes [NotNull.patient.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.name,name]; arguments []; default message [name]]; default message [Name Can not be Null]] [Field error in object 'patient' on field 'name': rejected value [null]; codes [NotEmpty.patient.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.name,name]; arguments []; default message [name]]; default message [Name Can not be Empty]] ",
      "errors": [
        "Name Can not be Null",
        "Name Can not be Empty"
      ]
    }
Arshad Ali
  • 3,082
  • 12
  • 56
  • 99
Niraj Sonawane
  • 10,225
  • 10
  • 75
  • 104
17

Try to override the method handleMethodArgumentNotValid, In my case the method become like this:

  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                HttpHeaders headers, HttpStatus status,
                                                                WebRequest request) {
    String errorMessage = ex.getBindingResult().getFieldErrors().stream()
                 .map(DefaultMessageSourceResolvable::getDefaultMessage)
                 .findFirst()
                 .orElse(ex.getMessage());
    return response(ex, request, HttpStatus.BAD_REQUEST, errorMessage);
  }

  private ResponseEntity<Object> response(Exception ex, WebRequest request, HttpStatus status,
                                          String message) {
    return handleExceptionInternal(ex, message, header(), status, request);
  }
Dayan Costa
  • 329
  • 2
  • 6
0

Your problem is extending from ResponseEntityExceptionHandler class. You can still use that handler for other exception types but for handling MethodArgumentNotValid it must be custom class&method.

There is a class for custom ResponseError object contains errorCode & errorMessage.

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class ValidationErrorHandler {
   
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public final ResponseEntity<ResponseError> handleMethodArgumentNotValidException(
            MethodArgumentNotValidException ex) {
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        ResponseError response = ResponseError.builder().errorCode(ErrorCode.INVALID_ARGUMENTS.getCode())
                .errorMessage(getMessages(ex.getBindingResult())).build();
        return new ResponseEntity<>(response, httpStatus);
    }

    private String getMessages(BindingResult bindingResult) {
        return Optional.ofNullable(bindingResult.getFieldError()).map(DefaultMessageSourceResolvable::getDefaultMessage)
                .orElse("");
    }
}
Emre Zorlu
  • 51
  • 1
  • 7
0

The class ResponseEntityExceptionHandler is an abstract class and have a final method annotated with @ExceptionHandler have signature as

@ExceptionHandler({
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            MethodArgumentNotValidException.class,
            MissingServletRequestPartException.class,
            BindException.class,
            NoHandlerFoundException.class,
            AsyncRequestTimeoutException.class
        })
    @Nullable
    public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {

So If in your application you have a method handling any of the above exception , then you need to override the method provided by ResponseEntityExceptionHandler. Suppose you want to handle MethodArgumentNotValidException in your application then your Handler class code should look like

@RestControllerAdvice
public final class WebRequestExceptionHandler extends ResponseEntityExceptionHandler{
    
@Override
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, value = HttpStatus.INTERNAL_SERVER_ERROR)
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

         List<String> errorList = ex
                    .getBindingResult()
                    .getFieldErrors()
                    .stream()
                    .map(fieldError -> fieldError.getDefaultMessage())
                    .collect(Collectors.toList());
            ErrorDetails errorDetails = new ErrorDetails(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errorList);
            
        return handleExceptionInternal(ex, errorDetails, headers, status, request);
    }
}

Note that in my WebRequestExceptionHandler class I have not annotated the method with @ExceptionHandler(MethodArgumentNotValidException.class) as it will result in Ambiguous mapping exception

@Data
@NoArgsConstructor
public class ErrorDetails {
     private HttpStatus status;
        private String message;
        private List<String> errors;

        public ErrorDetails(HttpStatus status, String message, List<String> errors) {
            super();
            this.status = status;
            this.message = message;
            this.errors = errors;
        }
}


Jay Yadav
  • 236
  • 1
  • 2
  • 10
0

For anyone using Spring WebFlux trying to catch and map the validation errors raised, you need to use WebExchangeBindException instead of MethodArgumentNotValidException.

An example can be found here: Spring Reactive - Exception Handling for Method Not Allowed Exception not triggering post Spring 3.0.0 & Java 17 upgrade

Japu_D_Cret
  • 632
  • 5
  • 18
0

I received this exception in Spring 3.1.0. It means that you need to @Overridemethod handleMethodArgumentNotValid insteed of @ExceptionHandler(MethodArgumentNotValidException.class)

My code :

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler{


  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(
      MethodArgumentNotValidException ex,
      HttpHeaders headers,
      HttpStatusCode status,
      WebRequest request) {
    var apiError = new ApiError(HttpStatus.resolve(status.value()), ex.getMessage());
    return new ResponseEntity<>(apiError, status);
  }

}
Tomasz
  • 884
  • 8
  • 12
0

In case anyone still need help with it, you can just override the method. Sample code that returns a Problem Detail and makes the detail human-readable.

 /**
   * Catches Jakarta validation exceptions, returns Problem Detail with appropriate message.
   * We're overriding Spring handler to provide better message detail (Standard is: Invalid request content.)
   * @param MethodArgumentNotValidException (Jakarta Validation exception)
   * @return Problem detail, with the list of the error messages represented as readable plain text (as according to ProblemDetail rfc)
   */
@Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
    List<String> errorMessages = ex.getBindingResult().getFieldErrors().stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());
    String plainMsg = String.join(",",errorMessages);
    return new ResponseEntity<> (problemDetailForStatusAndDetail(HttpStatus.BAD_REQUEST, plainMsg),HttpStatus.BAD_REQUEST);
  }
Kaue
  • 5
  • 4