In a Spring Boot app, I created a custom GlobalExceptionHandler and add the following method to handle ConstraintViolationException for invalid file type during upload process:
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ResponseEntity<Object> handleConstraintViolationException(
ConstraintViolationException ex,
WebRequest request) {
log.error("Invalid file type.", ex);
return buildErrorResponse(ex, HttpStatus.UNPROCESSABLE_ENTITY, request);
}
My buildErrorResponse works correctly and build proper responses for other handle methods. However, It adds "uploadFile.file:" prefix to my error message.
My questions:
Is there any problem with my handleConstraintViolationException implementation? If not, how can I fix that problem?
I think there is no need to create custom exception class as shown below for the exceptions that is already defined in javax.validation like ConstraintViolationException. Is that true?
Note: If you need to have a look at my GlobalExceptionHandler, it is something like on this GitHub.
Answer For Question 1: Your implementation not wrong but have some missing properties. For best practice to my opinion you can create a class to use it for error response which can contains like statusCode,timestamp,message,description.
You can use method like this in your handleConstraintViolationException method:
private ResponseEntity<ErrorMessage>generateErrorMessage(ConstraintViolationException ex, WebRequest request){
ErrorMessage errorMessage = new ErrorMessage.ErrorMessageBuilder()
.statusCode(HttpStatus.BAD_REQUEST.value())
.timeStamp(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now()))
.message(ex.getMessage())
.description(request.getDescription(false))
.build();
return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
}
Related
Im making RESTful API for practice.
I want to valid check request body and made ExceptionHandler.class like below.
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
List<String> errorList = ex
.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
ApiResponse apiResponse=new ApiResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), errorList, ex);
return new ResponseEntity<>(
apiResponse, HttpStatus.BAD_REQUEST
);
}
I made ApiResponse.class for response body in ResponseEntity<>.
#Data
#Slf4j
public class ApiResponse{
public HttpStatus status;
public String msg;
public List<String> err;
public Exception ex;
public ApiResponse(HttpStatus status, String msg, List<String> err, Exception ex){
this.status=status;
this.msg=msg;
this.err=err;
this.ex=ex;
}
}
but when MethodArgumentNotValidException happen, I got error like below...
Caused by:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class
org.springframework.validation.DefaultMessageCodesResolver and no
properties discovered to create BeanSerializer (to avoid exception,
disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference
chain: com.practice.skillup.restfulapi.dto.ApiResponse["ex"]-
what is the reason and how can I resolve it. (I'm just a web develop beginner it would be very thankful to explain easily)
This problem comes because entities are loaded lazily whereas the serialization process is performed before entities get loaded fully. Jackson tries to serialize the nested object but it fails as it finds JavassistLazyInitializer instead of the normal object.
As per your stack trace, You can suppress this error by adding the following configuration to your application.properties file:-
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false
This will only hide the error but won't solve the issue. To solve the issue you can use the add-on module for Jackson which handles Hibernate lazy-loading. See more here:- Jackson-datatype-hibernate, how to configure Jackson-datatype-hibernate.
Is it possible to add some custom validation message to path variable?
I have some GET
#GetMapping("/v2/tw/{id}")
public TwDto getTw(Authentication auth, #PathVariable Long id) {
In case of /v2/tw/someString I'd like to catch error and add some custom error message like "invalid tw ID"... How to do that? In ControllerAdvice add some ExceptionHandler?
For your particular use case, you can use #ExceptionHandler in the Controller or in a #ControllerAdvice class as shown here. For example, I am returning NOT_FOUND error for the sake of it.
#ExceptionHandler({MethodArgumentTypeMismatchException.class})
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "this is the reason")
public void handle() {
}
You may not see the reason in the actual error response, until you enable
server:
error:
include-message: always
If you think your #ExceptionHandler is only needed in a Controller class you can keep the method inside the controller. Alternatively you can create a #ControllerAdvice class and put the method there, so that you can reuse across multiple controllers in your application.
However, if you want a more complex validation, I will suggest to keep the id type to String and then cast manually into Long and perform the validation. Doing so you can throw your own RuntimeException and handle different cases.
I know it is possible to define custom exception handlers in Spring Boot and have e.g. the IllegalArgumentException exception mapped to the HTTP 400 Bad Request. I am wondering are there any existing exceptions defined in Spring Web/Boot mapped to the standard HTTP status codes? So that I just can throw them and they will be automatically mapped to the standard HTTP status codes.
Effectively, ResponseEntityExceptionHandler will, by default, transform Spring internally thrown exceptions to an HTTP status code. However, converting the exception to an HTTP status code does not provide any significant logs about the exception. Good security practices dictate that externally dispatched error message shall be the least informative possible about the internals of a system. Conversely logs shall be as informative as could be.
Moreover, the ResponseEntityExceptionHandler only handle Spring generated exceptions. All business related exceptions must be handled separately. For instance, a "Record not found" exception thrown from a findSomething(xxx) method is not handled by this class.
Following are examples on how to address these shortcomings:
Spring threw internal errors
You must override the handler of the exception(s) of interest and provide both an internal log message and an external error message to be returned to the caller.
The #ControllerAdvice is an annotation that wraps #Component classes with classes declaring #ExceptionHandler annotated methods. Simply put, these handlers will wrap all #Component methods.
#Slf4j
#ControllerAdvice
public class InternalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException e,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
LogError error = new LogError("MissingServletRequestParameterException",
HttpStatus.BAD_REQUEST,
String.format("Missing '%s' parameter", e.getParameterName()));
log.debug(error.toJson());
HttpErrorResponse response = new HttpErrorResponse(error.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
....
}
Business layer thrown errors
You must first create a specific RuntimeException class for each of these exceptions and annotated it woth #ResponseStatus.
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Record not found") //
public class RecordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8857378116992711720L;
public RecordNotFoundException() {
super();
}
public RecordNotFoundException(String message) {
super(message);
}
}
Then, you create an #ControllerAdvice annotated class that will hold all these exceptions handler method. There are no class to derive from as the internal redirection to these #ExceptionHandler annotated methods are managed by Spring.
#Slf4j
#ControllerAdvice
public class ClientExceptionHandler {
#ExceptionHandler(value = RecordNotFoundException.class)
public ResponseEntity<String> handleRecordNotFoundException(
RecordNotFoundException e,
WebRequest request) {
LogError logging = new LogError("RecordNotFoundException",
HttpStatus.NOT_FOUND,
request.getDescription(true));
log.info(logging.toJson());
HttpErrorResponse response = new HttpErrorResponse(logging.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
....
}
Finally, the helper classes LogError and HttpErrorResponse are simple formatters for their respective destination.
Hope this helps.
Jake
There is a handful e.g. HttpRequestMethodNotSupportedException which maps to 405.
Take a look at ResponseEntityExceptionHandler.handleException() method which defines basic rules for handling common exceptions in Spring MVC. You will find
NoSuchRequestHandlingMethodException.class,
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
I'm doing my REST documentation with swagger. I've set it up and got access on SwaggerUi and also see all my configured REST resources with their supported methods.
In my backend I have a ControllerAdvice, which does a global exception handling for all my controllers. A example exception which gets handled in the controller advice is ResourceAlreadyExistsException, when I try to create a resource which already exists, obviously. In that case my exception handler responds with a 409 CONFLICT status code.
#ExceptionHandler(value = ResourceAlreadyExistsException.class)
#ResponseStatus(HttpStatus.CONFLICT)
protected ErrorResponse handleResourceAlreadyExists(ResourceAlreadyExistsException ex, WebRequest request) {
return new ErrorResponse(ex.getMessage());
}
With this pre-condition, my create method which is mapped in the REST controller looks like this:
#RequestMapping(method = POST)
#ResponseStatus(HttpStatus.CREATED)
public RoleDto createRole(#RequestBody RoleDto roleDto) throws ResourceAlreadyExistsException {
return roleManager.createRole(roleDto);
}
With the default configuration, Swagger only shows me 201 as possible response code. Although 409 is possible too.
Of course I could add the #ApiResponse(code = 409, message = "Role already exists") definition to the createRole() method, but this seems double information as I already imply that by throwing the exception.
How can I tell swagger, that if a ResourceAlreadyExistsException can be be thrown, 409 is also a possible response code?
I've tried defining #ApiResponse on the ResourceAlreadyExistsException, but that didn't work.
That feature does not exist yet in SpringFox, although they have been looking for someone to implement it for quite some time now.
https://github.com/springfox/springfox/issues/521
I defined the annotation #ExceptionHandler in my rest controller ,but it doesn't work as i wished when i mark the result type as text/plain,the method was defined to handle an attach upload request . SpringMvc just throw my business exception to the servlet container.Is it a bug of SpringMVC?How can i fix it?
#RestController
#RequestMapping("/api/test")
public class TestController extends BasicController{
#RequestMapping(value="/uploadAttach", headers = ("content-type=multipart/*"),method = RequestMethod.POST,produces="text/plain")
public String test(){
throw new ServiceException("biz exception");
}
#ExceptionHandler(value = {ServiceException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
public #ResponseBody StatusMessage serviceError(ServiceException ex) {
return new StatusMessage(ex.getMessage());
}
}
Since nobody has interest to answer this problem,i have to solve it myself.
I found that the exception handler will look for the right converter to handle the exception result which is defined as return value of the #ExceptionHandler. In my controller ,the exception is a pojo,when i debuged the program,i found there was no converter can write the pojo to type "text/plain" ,so SpringMvc throw the original exception to the servlet container,and returned an error page by Jetty.
you should follow these tips below:
1.you should define a customized exception,
but it must extend Exception.class;
2.setting a global exception hadler,and the dao throws the exception to service,the service throws to controller, then the controller uses the try...catch.. codes block to solve it;
3.a bean should be configured in SpringMVC.xml
like this:
4.and that's all, you can test it by using some simple codes like 1/0;