I have defined a pattern for validating email in my Entity class. In my validation exception handler class, I have added handler for ConstraintViolationException. My appli
You cannot catch ConstraintViolationException.class
because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException
.
In my case, it's a TransactionSystemException
.
I'm using @Transactional
annotations from Spring with the JpaTransactionManager
. The EntityManager throws a rollback exception when somethings goes wrong in the transaction, which is converted to a TransactionSystemException
by the JpaTransactionManager
.
So you could do something like this:
@ExceptionHandler({ TransactionSystemException.class })
public ResponseEntity<RestResponseErrorMessage> handleConstraintViolation(Exception ex, WebRequest request) {
Throwable cause = ((TransactionSystemException) ex).getRootCause();
if (cause instanceof ConstraintViolationException) {
Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) cause).getConstraintViolations();
// do something here
}
}
I think you should add @ResponseStatus(HttpStatus.BAD_REQUEST)
to your @ExceptionHandler
:
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
List<String> errors = new ArrayList<String>();
....
}
Following solution is based on Spring Boot 2.1.2.
To clarify things... as nimai already correctly mentioned:
You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException.
In your case it is probably a DataIntegrityViolationException
, which points out a problem in the persistence layer. But you don't want to let it come that far.
Make use of the @Valid
annotation for the entity given as method parameter as Ena mentioned. On my version it was missing the org.springframework.web.bind.annotation.RequestBody
annotation (Without the @RequestBody
annotation the ProfileDto
cannot be parsed correctly into your ProfileDto
entity and the properties are resulting in null
values, e.g. NullPointerException
.):
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid @RequestBody ProfileDto profile){
...
}
This will then return your wanted status code 400 and some default response body accompanied by a org.springframework.web.bind.MethodArgumentNotValidException
before even reaching the persistence layer. The processing of the MethodArgumentNotValidException
is defined in org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
.
This is another topic, but you then have the option to override that behaviour by creating a @ControllerAdvice
with @ExceptionHandler(MethodArgumentNotValidException.class)
and customize the response body to your needs, since the default error response body is not optimal and not even present when excluding ErrorMvcAutoConfiguration.
Caution: Locating the @ExceptionHandler(MethodArgumentNotValidException.class)
inside the @ControllerAdvice
that extends the ResponseEntityExceptionHandler
results into an IllegalStateException
, because in the ResponseEntityExceptionHandler
already is an exception handler defined for MethodArgumentNotValidException
. So just put it into another @ControllerAdvice
class without extending anything.
I saw you can also trigger the validation of the email pattern manually (see Manually call Spring Annotation Validation). I didn't test it myself, but I personally don't like that approach, because it is just bloating your controller code and I currently can't think of a use case that requires it.
I hope that helps others encountering a similar issue.
You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException. So you could do something like this:
@ExceptionHandler({TransactionSystemException.class})
protected ResponseEntity<Object> handlePersistenceException(final Exception ex, final WebRequest request) {
logger.info(ex.getClass().getName());
//
Throwable cause = ((TransactionSystemException) ex).getRootCause();
if (cause instanceof ConstraintViolationException) {
ConstraintViolationException consEx= (ConstraintViolationException) cause;
final List<String> errors = new ArrayList<String>();
for (final ConstraintViolation<?> violation : consEx.getConstraintViolations()) {
errors.add(violation.getPropertyPath() + ": " + violation.getMessage());
}
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, consEx.getLocalizedMessage(), errors);
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
final ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
Try this way..
@ControllerAdvice
public class ControllerAdvisor extends ResponseEntityExceptionHandler {
@Autowired
BaseResponse baseResponse;
@ExceptionHandler(javax.validation.ConstraintViolationException.class)
public ResponseEntity<BaseResponse> inputValidationException(Exception e) {
baseResponse.setMessage("Invalid Input : " + e.getMessage());
return new ResponseEntity<BaseResponse>(baseResponse, HttpStatus.BAD_REQUEST);
}
}