I want that when an error is encountered, the user receives in the exception response to his http call.
The error is indeed caught, displayed in the spring logs, but it is not returned to the user.
Why ?
My method :
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) {
try {
// Check for authorization header existence.
String header = request.getHeader(JwtConstant.AUTHORIZATION_HEADER_STRING);
if (header == null || !header.startsWith(JwtConstant.TOKEN_BEARER_PREFIX)) {
chain.doFilter(request, response);
return;
}
// Validate request..
UsernamePasswordAuthenticationToken authorization = authorizeRequest(request);
SecurityContextHolder.getContext().setAuthentication(authorization);
chain.doFilter(request, response);
} catch (Exception e) {
SecurityContextHolder.clearContext();
throw new InternalServerErrorException("Erreur sur le filtre interne -> " + e.toString()); // Should return this to the user
}
}
The error in logs :
.common.application.exception.InternalServerErrorException: Erreur sur le filtre interne ->
.common.application.exception.InternalServerErrorException: Erreur lors du authorizeRequest
The error Handler :
#ControllerAdvice
public class ApiResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
String typeDateFormat = "yyyy-MM-dd HH:mm:ss";
...
#ExceptionHandler(InternalServerErrorException.class)
public final ResponseEntity<ExceptionResponse> handleInternalServerErrorException(InternalServerErrorException ex,
WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(
new SimpleDateFormat(typeDateFormat).format(new Date()), ex.getMessage(),
request.getDescription(false), "500");
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
InternalServerErrorException.java
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerErrorException extends RuntimeException {
private static final long serialVersionUID = 1L;
public InternalServerErrorException(String exception) {
super(exception);
}
}
ExceptionResponse.java
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ExceptionResponse {
private String date;
private String message;
private String uri;
private String status;
}
The user receive a 403.. i don't know why, but should receive the error that i throw, without any body..
The InternalServerException works outside of the try/catch..
Basically #ControllerAdvice will only work if the exception is thrown from the #Controller / #RestContoller method.
Technically , the spring-mvc framework stuff (i.e #ControllerAdvice) will only take effect for an HTTP request if it can be successfully passed through all the Filter in the SecurityFilterChain (configured by spring-security) and processed by the DispatcherServlet. But now it seems that your exception is thrown from one of the Filter in the SecurityFilterChain
before the request reach to DispatcherServlet , and hence #ControllerAdvice will not be invoked to handle this exception.
You have to consult about if there are related configuration in that spring-security filter that allow you to customize the exception. If not , you can still manually do it in that filter since you can access HttpServletResponse from there.
Related
I am currently trying to figure out the best way to handle errors in a spring boot app.
Assume that the app consists of thymeleaf templates served on paths starting with /admin, and a REST API served on other URIs.
My project currently has the following configuration:
Thymeleaf error templates located at /src/main/resources/templates/error/{id}.html, defined for errors 400, 401, 403, 404, 500.
Default thymeleaf error template /src/main/resources/templates/error.html
HttpSecurity configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
(...)
http
.anonymous();
http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()))
.accessDeniedHandler(new AccessDeniedHandlerImpl());
(...)
}
Exception Handlers for both rest and MVC:
#ControllerAdvice
public class ExceptionHandlers {
private final BasicErrorController basicErrorController;
public ExceptionHandlers(BasicErrorController basicErrorController) {
this.basicErrorController = basicErrorController;
}
#ExceptionHandler(Exception.class)
public Object handleAllExceptions(Exception e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(EntityNotFoundException.class)
public Object handleEntityNotFoundException(EntityNotFoundException e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.NOT_FOUND, I18nCodes.ENTITY_NOT_FOUND);
}
/**
* We exclude all exceptions deriving from {#link AccessDeniedException} from custom exception handling.
*/
#ExceptionHandler(AccessDeniedException.class)
public Object handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
/**
* We exclude all exceptions deriving from {#link AppBaseException} from custom exception handling.
*/
#ExceptionHandler(AppBaseException.class)
public Object handleAppBaseException(AppBaseException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR, I18nCodes.getCodeByStatus(status));
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status, String message) {
String header = request.getHeader("Accept");
if (header != null && header.contains("text/html")) {
setErrorCode(request, response, status);
return basicErrorController.errorHtml(request, response);
}
return createJsonResponse(message, status, request.getRequestURI());
}
private ResponseEntity<ErrorResponseDTO> createJsonResponse(String message, HttpStatus status, String path) {
ErrorResponseDTO errorResponseDTO = new ErrorResponseDTO()
.setTimestamp(new Timestamp(System.currentTimeMillis()))
.setStatus(status.value())
.setMessage(message)
.setPath(path)
.setError(status.name().toLowerCase());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return ResponseEntity.status(status).headers(httpHeaders).body(errorResponseDTO);
}
private void setErrorCode(HttpServletRequest request, HttpServletResponse response, HttpStatus httpStatus) {
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, httpStatus.value());
response.setStatus(httpStatus.value());
}
}
Without the Exception handlers everything works about as expected, I get 401 error when I am not authenticated, and 403 when authenticated but lacking authorities. My custom exceptions are correctly mapped according to their message and Status defined in #ResponseStatus eg:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class AccountInfoException extends AppBaseException {
protected AccountInfoException(String message) {
super(message);
}
protected AccountInfoException(String message, Throwable cause) {
super(message, cause);
}
public static AccountInfoException emailAlreadyExists() {
return new AccountInfoException(I18nCodes.EMAIL_EXIST);
}
public static AccountInfoException accountNotFound() {
return new AccountInfoException(I18nCodes.ACCOUNT_NOT_FOUND);
}
}
Will map to status 400 with one of two messages I18nCodes.EMAIL_EXIST or I18nCodes.ACCOUNT_NOT_FOUND.
I arrive on the correct thymeleaf error page depending on the status code - life is perfect.
The issue comes with the requirement of handling all other exceptions. It's not a possibility to let an unexpected error to the client. So for that I've defined an exception handler handing Exception, to return a generic 500 message. This breaks everything. I'm unable to access the Status codes of my custom exceptions
Authentication entry point and access denied handler are both ignored, and both cases of being not authenticated and having insufficient authority are handles in the same exception(AccessDeniedException). I also lose default mappings for some exceptions eg. org.springframework.security.authentication.LockedException returns status 401.
My temporary workaround was to handle all exceptions, and to create methods handling exceptions that I want to ignore and just rethrow the exception. I feel like the best thing I could do is remove all exception handlers and repack all container exceptions to my own custom exceptions, only issue is that there doesn't seem to be a way to do it.
I'm looking for a more permanent solution - what would be the best approach?
We're running DropWizard and trying to drop in logging of the URLs that cause 404 responses to be thrown
We have a catchall exception mapper that receives a NotFoundException. Frustratingly that exception doesn't carry context of which URL caused it to be thrown.
Example application here: https://github.com/pauldambra/not.found.example
We're using an ExceptionMapper
public class NotFoundLogger implements ExceptionMapper<NotFoundException> {
ExampleLogger logger = new ExampleLogger();
#Override
public Response toResponse(final NotFoundException exception) {
logger.error(urlFrom(exception), exception);
return Response.status(404).build();
}
private String urlFrom(final NotFoundException exception) {
return "why is this not a property on the exception?!";
}
private class ExampleLogger {
void error(final String notFoundUrl, final NotFoundException exception) {
System.out.println("someone tried to load " + notFoundUrl);
System.out.println(exception.getMessage());
}
}
}
If we look at the application logs when someone requests a URL that the application doesn't serve we see that the application can log that it is returning a 404 for a path but our custom logger has no access to the URL
someone tried to load why is this not a property on the exception?!
HTTP 404 Not Found
127.0.0.1 - - [08/May/2019:09:53:47 +0000] "GET /ping/pong HTTP/1.1" 404
Is an ExceptionMapper the wrong way to do this?
Turns out there are two ways
one does use the exception mapper:
public class NotFoundLogger implements ExceptionMapper<NotFoundException> {
// magically inject a thing
// remember that magic is for evil wizards
#Context
private HttpServletRequest request;
private ExampleLogger logger = new ExampleLogger();
#Override
public Response toResponse(final NotFoundException exception) {
final StringBuffer absolutePath = HttpUtils.getRequestURL(request);
logger.error("exception mapper: " + absolutePath, exception);
return Response.status(404).build();
}
}
This works but isn't very discoverable.
You can also add a response filter
public class NotFoundLoggingFilter implements ContainerResponseFilter {
private ExampleLogger logger = new ExampleLogger();
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) {
if (responseContext.getStatus() != 404) {
return;
}
final URI absolutePath = requestContext.getUriInfo().getAbsolutePath();
logger.error("filter: " + absolutePath, new NotFoundException());
}
}
This doesn't require any magic so suits me but you can choose your poison.
Really the path should be on the NotFoundException - if I had more time I'd propose the code change to add it.
Here is a problem: I have a controller that takes an input model. Lets say
public class AppUserUpdateData {
#NotNull
#Size(min = 1, max = 50)
protected String login;
#JsonDeserialize(using = MyDateTimeDeserializer.class)
protected Date startWorkDate;
*************
other properties and methods
*************
}
The problem is when I want to restrict a down board of a date I eventually get an HTTP exception 400 without any messages despite I handle this case in my code!
here is a controller:
#RequestMapping(
value = "/users/{userId}", method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public #ResponseBody AbstractSuccessResult updateUser(#PathVariable Long userId,
#RequestBody AppUserUpdateData appUserUpdateRequest, HttpServletRequest request) {
AbstractSuccessResult response = new AbstractSuccessResult();
appUserService.updateUser(appUserUpdateRequest, userId);
return response;
}
Here is a Deserializer:
public class MyDateTimeDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
try {
return DataTypeHelper.stringToDateTime(jsonParser.getText());
} catch (MyOwnWrittenException ex) {
throw ex;
}
}
}
In a DataTypeHelper.stringToDateTime are some validations that are blocking invalid date-strings.
And there is a handler for a my exception:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
The problem is that when an exception in a MyDateTimeDeserializer has been thrown it doesn't falling into a MyExceptionHandler but I cannot understand why? What am I doing wrong?
In the response is just an empty response with a code 400(
UPD
Thanks to #Joe Doe's answer the problem has been solved. Here is my updated handler:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Throwable cause = ex.getCause();
String message = null;
if (cause instanceof JsonMappingException) {
if (cause.getCause() instanceof MyOwnWrittenException) {
return handleInvalidRequest((RuntimeException) cause.getCause(), request);
} else {
message = cause.getMessage();
}
} else {
message = ex.getMessage();
}
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(
new BasicErrorMessage(message));
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(ex, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
UPD
In my project it doesn't work without annotation #Order(Ordered.HIGHEST_PRECEDENCE)
I believe that is because of number of ControllerAdvices in a project
Before updateUser in your controller gets invoked, its arguments have to be resolved. This is where HandlerMethodArgumentResolverComposite comes in, and delegates to one of pre-registered HandlerMethodArgumentResolvers - in this particular case it delegates to RequestResponseBodyMethodProcessor.
By delegating I mean calling the resolver's resolveArgument method. This method indirectly calls the deserialize method from your deserializer, which throws an exception of type MyOwnWrittenException. The problem is that this exception gets wrapped in another exception. In fact, by the time it propagates back to resolveArgument, it's of type HttpMessageNotReadableException.
So, rather than catching MyOwnWrittenException in your custom exception handler, you need to catch exceptions of type HttpMessageNotReadableException. Then, in the method that handles that case, you can check whether the "original" exception was in fact MyOwnWrittenException - you can do that by repeatedly calling the getCause method. In my case (it's probably going to be the same in yours), I needed to call getCause twice to "unwrap" the original exception (HttpMessageNotReadableException -> JsonMappingException -> MyOwnWrittenException).
Note that you can't simply substitute MyOwnWrittenException with HttpMessageNotReadableException in your exception handler since it clashes (at runtime) with another method, specifically designed to handle exceptions of the latter type, called handleHttpMessageNotReadable.
In summary, you can do something like this:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// ex.getCause().getCause().getClass() gives MyOwnWrittenException
// the actual logic that handles the exception...
}
}
I created a filter which authenticate each request header for JWT token:
public class JWTAuthenticationFilter extends GenericFilterBean {
private UserDetailsService customUserDetailsService;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JWTAuthenticationFilter(UserDetailsService customUserDetailsService) {
this.customUserDetailsService = customUserDetailsService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
SecurityContextHolder.getContext().setAuthentication(authentication);
if (authentication == null) {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
filterChain.doFilter(request, response);
}
}
I want to throw a custom exception, and that exception returns a response:
#ResponseStatus(value=HttpStatus.SOMECODE, reason="There was an issue with the provided authentacion information") // 409
public class CustomAuthenticationException extends RuntimeException {
private static final long serialVersionUID = 6699623945573914987L;
}
How should I do this ? What is the best design to catch such exception thrown by filters ?
Is there any kind of exception handling mechanism provided by the Spring security that I can use and catch everythin in one point ?
Is there any other way to throw custom exceptions in a filter ?
Note: there is another question here which its accepted answer doesn't answer my question. I want to return a response before getting to any controller.
Error cases I want to handle:
1. Client sends an empty value for the Authorization header.
2. Client sends a malformed token
In both cases I get a response with 500 HTTP status code. I want to get 4XX code back.
Take a look at #ControllerAdvice
Here's an example from my project.
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
private final Logger log = Logger.getLogger(this.getClass().getSimpleName());
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = RuntimeException.class)
public Response handleBaseException(RuntimeException e) {
log.error("Error", e);
Error error = new Error(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.name());
return Response.status(HttpStatus.BAD_REQUEST.value()).error(error, null).build();
}
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(value = NoHandlerFoundException.class)
public Response handleNoHandlerFoundException(Exception e) {
log.error("Error", e);
Error error = new Error(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.name());
return Response.status(HttpStatus.NOT_FOUND.value()).error(error, null).build();
}
#ExceptionHandler(value = AuthenticationCredentialsNotFoundException.class)
public Response handleException(AuthenticationCredentialsNotFoundException e) {
log.error("Error", e);
Error error = new Error(ErrorCodes.INVALID_CREDENTIALS_CODE, ErrorCodes.INVALID_CREDENTIALS_MSG);
return Response.status(ErrorCodes.INVALID_CREDENTIALS_CODE).error(error, null).build();
}
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = UnauthorisedException.class)
public Response handleNotAuthorizedExceptionException(UnauthorisedException e) {
// log.error("Error", e);
return Response.unauthorized().build();
}
#ExceptionHandler(value = Exception.class)
public String handleException(Exception e) {
log.error("Error", e);
return e.getClass().getName() + " 14" + e.getMessage();
}
}
Edit
I believe you can response.sendError inside do Filter method.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
SecurityContextHolder.getContext().setAuthentication(authentication);
if (authentication == null) {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid authentication.");
setUnauthorizedResponse(response);
return;
}
filterChain.doFilter(request, response);
}
public void setUnauthorizedResponse(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
Response unAuthorizedResponse = Response.unauthorized().build();
try {
PrintWriter out = response.getWriter();
out.println(unAuthorizedResponse.toJsonString());
} catch (IOException e) {
log.error("Error", e);
}
}
I had the same issue with JWT tokens and posted the solution on this question, since the issue there was similar (he had trouble with filter exceptions)
Disclaimer: This is not the answer to the question asked, but this is a followup answer to the problem which Arian was asking.
As commented above, please see how you can autowire in places which are launched before spring container gives us access to beans.
Here I am autowiring my BlacklistJwtRepo
if (blacklistJwtRepo == null) { //Lazy Load because filter
ServletContext servletContext = req.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
}
This is where I am getting hold of the req object -
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
Final code looks like -
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("blacklistJwtRepo : " + blacklistJwtRepo);
//till here the autowired repo (blacklistJwtRepo) is null
if (blacklistJwtRepo == null) { //Lazy Load because filter
ServletContext servletContext = req.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
}
I have a domain class defined as follows
#Data
#Entity
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long cityID;
#NotBlank(message = "City name is a required field")
private String cityName;
}
When I post to the endpoint http://localhost:8080/cities without a cityName I get a ConstraintViolationException but when I send a PUT request to the endpoint http://localhost:8080/cities/1 without a cityName I get the following exception instead of ConstraintViolationException.
{
"timestamp": 1494510208982,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.transaction.TransactionSystemException",
"message": "Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction",
"path": "/cities/1"
}
So how do I get a ConstraintViolationException exception for a PUT request?
Note: I am using Spring Data Rest so the endpoints are generated by Spring. There is no custom rest controller.
I think Cepr0's test work for both PUT and POST, because when you send a PUT request for a non-existing entity then Spring Data Rest uses the create method in the background.
Let's assume there is no user with id=100:
calling 'PUT users/100' is the same as calling 'POST users/'
When you send PUT for an existing entity, it will generate that nasty TransactionSystemException.
I'm also fighting with the Data Rest exception-handling right now, and there are a lot of inconsistency in there.
Here is my current RestErrorAttributes class, it solves most of my problems, but there is a good chance I will fond others during the following days. :)
#Component
#Slf4j
public class RestErrorAttributes extends DefaultErrorAttributes implements MessageSourceAware {
private MessageSource messageSource;
#Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/** {#inheritDoc} */
#Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
final Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
// Translate default message by Locale
String message = errorAttributes.get("message").toString();
errorAttributes.put("message",
messageSource.getMessage(message, null, message, LocaleContextHolder.getLocale()));
// Extend default error message by field-errors
addConstraintViolationDetails(errorAttributes, requestAttributes);
return errorAttributes;
}
private void addConstraintViolationDetails(Map<String, Object> errorAttributes,
RequestAttributes requestAttributes) {
Throwable error = getError(requestAttributes);
if (error instanceof ConstraintViolationException) {
errorAttributes.put("errors",
RestFieldError.getErrors(((ConstraintViolationException) error).getConstraintViolations()));
}
else if (error instanceof RepositoryConstraintViolationException) {
errorAttributes.put("errors", RestFieldError
.getErrors(((RepositoryConstraintViolationException) error).getErrors().getAllErrors()));
}
}
/** {#inheritDoc} */
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
try {
Throwable cause = ex;
while (cause instanceof Exception) {
// Handle AccessDeniedException - It cannot be handled by
// ExceptionHandler
if (cause instanceof AccessDeniedException) {
response.sendError(HttpStatus.FORBIDDEN.value(), cause.getMessage());
super.resolveException(request, response, handler, (Exception) cause);
return new ModelAndView();
}
// Handle exceptions from javax validations
if (cause instanceof ConstraintViolationException) {
response.sendError(HttpStatus.UNPROCESSABLE_ENTITY.value(), "validation.error");
super.resolveException(request, response, handler, (Exception) cause);
return new ModelAndView();
}
// Handle exceptions from REST validator classes
if (cause instanceof RepositoryConstraintViolationException) {
response.sendError(HttpStatus.UNPROCESSABLE_ENTITY.value(), "validation.error");
super.resolveException(request, response, handler, (Exception) cause);
return new ModelAndView();
}
cause = ((Exception) cause).getCause();
}
} catch (final Exception handlerException) {
log.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return super.resolveException(request, response, handler, ex);
}
#Getter
#AllArgsConstructor
public static class RestFieldError {
private String field;
private String code;
private String message;
public static List<RestFieldError> getErrors(Set<ConstraintViolation<?>> constraintViolations) {
return constraintViolations.stream().map(RestFieldError::of).collect(Collectors.toList());
}
public static List<RestFieldError> getErrors(List<ObjectError> errors) {
return errors.stream().map(RestFieldError::of).collect(Collectors.toList());
}
private static RestFieldError of(ConstraintViolation<?> constraintViolation) {
return new RestFieldError(constraintViolation.getPropertyPath().toString(),
constraintViolation.getMessageTemplate(), constraintViolation.getMessage());
}
private static RestFieldError of(ObjectError error) {
return new RestFieldError(error instanceof FieldError ? ((FieldError) error).getField() : null,
error.getCode(), error.getDefaultMessage());
}
}
}
My workaround for this was to setup an exception handler to handle the TransactionSystemException, unwrap the exception and handle like a regular ConstraintViolationException:
#ExceptionHandler(value = {TransactionSystemException.class})
public ResponseEntity handleTxException(TransactionSystemException ex) {
Throwable t = ex.getCause();
if (t.getCause() instanceof ConstraintViolationException) {
return handleConstraintViolation((ConstraintViolationException) t.getCause(), null);
} else {
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex,
WebRequest request) {
List<String> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": " + violation.getMessage());
}
return new ResponseEntity<>(errors, new HttpHeaders(), HttpStatus.CONFLICT);
}