Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]

Spring 4

Spring 4 Problem Overview


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.

Spring 4 Solutions


Solution 1 - Spring 4

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.

Solution 2 - Spring 4

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

Solution 3 - Spring 4

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"      ]
    }

Solution 4 - Spring 4

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKrishnaView Question on Stackoverflow
Solution 1 - Spring 4Stefan Isele - prefabware.comView Answer on Stackoverflow
Solution 2 - Spring 4Dayan CostaView Answer on Stackoverflow
Solution 3 - Spring 4Niraj SonawaneView Answer on Stackoverflow
Solution 4 - Spring 4Emre ZorluView Answer on Stackoverflow