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);
}
Related
I have created a controller advice class to return JSON error responses.
It does not respond with JSON when I get a HttpMessageNotReadableException, however I still get a "Resolved [org.springframework.http.converter.HttpMessageNotReadableException:..." log in my terminal.
My exception handler class:
#ControllerAdvice
public class PosterExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
RuntimeException.class,
})
protected ResponseEntity<ExceptionResponse> internalServerErrorHandler(RuntimeException e) {
return this.defaultResponseHandler(e, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(value = {
UserNotFoundException.class,
UserExistsException.class,
InvalidTokenException.class
})
public ResponseEntity<ExceptionResponse> badRequestResponseHandler(RuntimeException e) {
return this.defaultResponseHandler(e, HttpStatus.BAD_REQUEST);
}
public ResponseEntity<ExceptionResponse> defaultResponseHandler(RuntimeException e, HttpStatus status) {
var resp = new ExceptionResponse(
status.value(),
e.getMessage(),
Instant.now()
);
return new ResponseEntity<>(resp, status);
}
}
The exception I receive is org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<java.lang.Object> com.sulayman.poster.controller.PostController.post(com.sulayman.poster.dto.PostRequestDto
I expected the application to return a JSON response with the error
Because your PosterExceptionHandler extends by ResponseEntityExceptionHandler, which intercept HttpMessageNotReadableException
else if (ex instanceof HttpMessageNotReadableException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
}
If you want to handle the HttpMessageNotReadableException themselves, you need delete extend ResponseEntityExceptionHandler.
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.
I have a ControllerExceptionHandler with ControllerAdvise and when in application is thrown generalException or customException then i except to catch it in this ControllerExceptionHandler . But it doesn't happen. It looks very simple, read many sites , but it's not triggering.
Don't know where is the problem.
It's ControllerExceptionHandler.class
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);
#ExceptionHandler(Exception.class)
public final ResponseEntity<?> handleGeneralErrors(Exception e, UserPrincipal userPrincipal) {
if (e instanceof ClientAbortException) {
LOGGER.error("REST client abort: {}", e.getMessage());
} else {
LOGGER.error("REST controller error.", e);
}
//somelogic
return new ResponseEntity<>(responseObject, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(MyCustomException.class)
public final ResponseEntity<?> handleCustomErrors(MyCustomException e, UserPrincipal userPrincipal) {
LOGGER.error("MyCustomException error.", e);
return new ResponseEntity<>(
responseObject,
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Controller.class
#RestController
#RequestMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
public class Controller {
private static final Logger LOGGER = LoggerFactory.getLogger(Controller .class);
#Autowired
private SomeLogicClass someLogicClass;
#GetMapping("/check")
#ResponseBody
public ResponseEntity<List<City>> list(UserPrincipal userPrincipal) throws Exception {
//Some logic
return ResponseEntity.ok(someLogicClass.handleRequest());
}
SomeLogicClass.class
#Service
public class SomeLogicClass{
public void handleRequest() throws Exception {
//some logic
} catch (Exception ex) {
throw new MyCustomException();
}
}
}
So, when the request is reaching SomeLogicClass, then some Exception(for example NPE) is thrown and then i want to throw in catch myCustomException. I expect that it will go to ControllerAdvise, but nothing and i see error 500.
Something is missing here or what? Controller and ControllerAdvise are located in some package.
I also tried to add the package
#ControllerAdvice("my.package.controller")
Found what caused that it skipped ControllerAdvise. The problem was UserPrinciple object, that is actually my custom class. Any custom class that i put additionally in input argument was a problem. I don't know the reason for this , but implemented without UserPrinciple.class.
I have a method which should throw an exception if the input is blank. The input, in this case, a list, will throw an exception if it is empty. I have debugged this using JUnit and can confirm that passing in [] would throw the exception. In my front end application however, passing in an empty list still shows the response status as 200 (as seen in the developer console). Shouldn't throwing an exception generate a response status of 500? At least that's what I'm looking for, so that way I can respond with an error message to the user. How can I get the below to generate a response status of 500? I'm using spring framework.
public Boolean validate(List<HashMap<String, Object>> myList) throws MyException{
Boolean hasErrors = false;
if(!myList.isEmpty()){
//logic goes here
....
} else{
hasErrors = true;
throw new MyException("myList shouldn't be empty ");
}
return hasErrors;
}
Exception class here:
public class MyException extends Exception {
public MyException() { super(); }
public MyException(String message) { super(message); }
public MyException(String message, Throwable cause) { super(message, cause); }
public MyException(Throwable cause) { super(cause); }
}
Any help would be appreciated.
I would suggest that you use proper exception handling using Spring MVC, that way, it would be much more clear on what should be returned as status code when throwing an exception.
For example, you can do something like this in your exception class:
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR, reason="myList shouldn't be empty")
public class MyException extends Exception {
public MyException() { super(); }
public MyException(String message) { super(message); }
public MyException(String message, Throwable cause) { super(message, cause); }
public MyException(Throwable cause) { super(cause); }
}
You can use a #ControllerAdvice to handle exception that you throw then specify what return status you want to the client.
Here is an example that I have that I test to see if the file the user is uploading is too large for the website and return a bad request (html code 400).
The toExceptionResponeString is just a method that converts my exception to a json string, any string will do for the return.
You can use your MyException class in lieu of the MaxUploadSizeExceededException and handle it from there.
#ControllerAdvice
public class ExceptionController extends ResponseEntityExceptionHandler {
#Autowired
private ApplicationLogService appLogService;
#ExceptionHandler(value = { MaxUploadSizeExceededException.class })
protected ResponseEntity<Object> handleMaxUploadSizeExceededException(Exception ex, WebRequest request) {
MaxUploadSizeExceededException musee = (MaxUploadSizeExceededException)ex;
SizeLimitExceededException slee = musee.getCause() instanceof SizeLimitExceededException ? (SizeLimitExceededException) musee.getCause() : null;
Long maxSize = slee == null ? musee.getMaxUploadSize() : slee.getPermittedSize();
Long actualSize = slee == null ? Long.valueOf(request.getHeader("Content-Length")) : slee.getActualSize();
PortalException pre = PortalException.fileSizeTooLarge(maxSize, actualSize);
try {
appLogService.addExceptionEntry(pre.getMessage());
} catch (Exception e) { }
return handleExceptionInternal(ex, toExceptionResponseString(pre), new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
}
I'm working on a web app using Spring MVC and AngularJS, I'm creating a Rest API that returns ResponseEntities that contains JSON strings.
I want to be able when an Exception happens to return a string that contains the error cause to my view and then show this error with a modal in AngularJS, I created a Class with the #ControllerAdvice annotation and in this class I defined a method with my custom exception like this
#ControllerAdvice
public class GlobalExceptionHandlerController {
#ExceptionHandler(PersonalException.class)
public String handleCustomExceptionRazon(PersonalException ex) {
String errorMessage = "custom error";
return errorMessage;
}
}
I have the following interface
public interface ClientDAO {
public void insertCLiente(Client client) throws PersonalException
}
And in my implementation
#Override
public void insertCLiente(Client client) throws PersonalException{
//method implementation
if (searchCLiente(client.name())) {
throw new PersonalException("client aleady exists");
} else {
//method implementation
}
}
My searchClient Method
public boolean searchClient(String name) {
try {
//method implementation
} catch (DataAccessException dataAccessException) {
System.out.println("");
dataAccessException.printStackTrace();
} catch (Exception e) {
System.out.println("");
e.printStackTrace();
}
//method implementation
}
My Client Controller
#Autowired
ClientDAO clientDAO;
#RequestMapping(value = "/client/", method = RequestMethod.POST)
public ResponseEntity<Void> createClient(#RequestBody final String DTOClientData, UriComponentsBuilder ucBuilder) {
//here I parse the JSON data and create my Client object
//here I dont know how can I return the error message
clientDAO.insertClient(client);
}
My custom Exception
public class PersonalException extends Exception {
public PersonalException (String msg) {
super(msg);
}
}
I don't know un my clientController method createClient how can I return an execption of the type PersonalException that I created
//here I dont know how can I return the error message
Just throw the exception from Controller.
#RequestMapping(value = "/client/", method = RequestMethod.POST)
public ResponseEntity<Void> createClient(#RequestBody final String DTOClientData, UriComponentsBuilder ucBuilder) throws PersonalException {
You can return error message in GlobalExceptionHandlerController like this...
/**
* REST exception handlers defined at a global level for the application
**/
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { PersonalException.class })
protected ResponseEntity<RestResponse> handleUnknownException(PersonalException ex, WebRequest request) {
LOGGER.error(ex.getMessage(), ex);
return new ResponseEntity<RestResponse>(new RestResponse(Boolean.FALSE, ImmutableList.of("Exception message"), null), HttpStatus.INTERNAL_SERVER_ERROR);
}
Now, you might have noticed that we are not handling the Exception even in the Controller. Instead, we are Throwing it in the declaration hoping that somewhere we have handled this exceptional case gracefully showing the User a nice looking Toaster message.
The question may remains – Where the hell i am handling the Exception? It is handling by the #ExceptionHandler in GlobalExceptionHandlerController .