Reputation: 1985
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 a custom message. What mistake I'm making? Please help.
Upvotes: 87
Views: 65481
Reputation: 11622
The only solution that I can find for this problem is to copy the method signature, remove the @Exception annotation and use @Override
as follows.
Spring Boot Version: 3.2.4
@Override // optional but important to refer to the overridden method
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.NOT_FOUND.value(),
"Cannot be found");
return new ResponseEntity<>(message, HttpStatus.NOT_FOUND);
}
Here is the full Global Exception Handler;
package io.github.bzdgn.jwtapp.error;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// Handles generic exceptions
@ExceptionHandler({Exception.class})
public ResponseEntity<ErrorMessage> handleAllExceptions(Exception ex, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"An error occurred");
return new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR);
}
@Override // optional but important to refer to the overridden method
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.BAD_REQUEST.value(),
"Bad request due to the argument");
return new ResponseEntity<>(message, HttpStatus.BAD_REQUEST);
}
@Override // optional but important to refer to the overridden method
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.NOT_FOUND.value(),
"Cannot be found");
return new ResponseEntity<>(message, HttpStatus.NOT_FOUND);
}
}
The reason is, as @Jay Yadav mentioned, you are extending the ResponseEntityExceptionHandler class. This class has already defined the default exception handlers and you need to override them.
Upvotes: 0
Reputation: 1426
I received this exception in Spring 3.1.0. This error means that you need to @Override method 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);
}
}
Upvotes: 3
Reputation: 9546
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.
Upvotes: 150
Reputation: 15
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);
}
Upvotes: 0
Reputation: 638
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
Upvotes: 0
Reputation: 440
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;
}
}
Upvotes: 1
Reputation: 131
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("");
}
}
Upvotes: 4
Reputation: 11115
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"
]
}
Upvotes: 20
Reputation: 339
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);
}
Upvotes: 17