Reputation: 2106
I am developing a RESTful app using Spring4. I want to handle the case when a POST request contains no body. I wrote the following custom exception handler:
@ControllerAdvice
public class MyRestExceptionHandler {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<MyErrorResponse> handleJsonMappingException(JsonMappingException ex) {
MyErrorResponse errorResponse = new MyErrorResponse("request has empty body");
return new ResponseEntity<MyErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Throwable.class)
public ResponseEntity<MyErrorResponse> handleDefaultException(Throwable ex) {
MyErrorResponse errorResponse = new MyErrorResponse(ex);
return new ResponseEntity<MyErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
@RestController
public class ContactRestController{
@RequestMapping(path="/contact", method=RequestMethod.POST)
public void save(@RequestBody ContactDTO contactDto) {...}
}
When it receives a POST with no body, these methods aren't called. Instead, the client gets a response with 400 BAD REQUEST HTTP status and empty body. Does anybody know how to handle it?
Upvotes: 20
Views: 93091
Reputation: 317
hmm..., i think you don't need to write a custom error response class, if you just want to inform the caller that the request body is empty. i think you can rely on the default spring error handling, but with additional setting.
first, add the @Valid annotation to your method parameter:
@RestController
public class ContactRestController{
@RequestMapping(path="/contact", method=RequestMethod.POST)
public void save(@Valid @RequestBody ContactDTO contactDto) {...}
}
(btw. for this you need to add the "spring-boot-starter-validation" dependency)
and add following in you application.yaml
:
server:
error:
include-message: always
This results in something like
{
...,
"status": 400,
"error": "Bad Request",
"message": "Required request body is missing: public org.springframework.http.ResponseEntity<...",
...
}
(fiy: it's said that sensitive information can be disclosured by doing this, so think about if it's okay :) )
Upvotes: 0
Reputation: 11
@ControllerAdvice
@ResponseBody
public class ControllerExceptionHandler {
@ExceptionHandler(BadCredentialsException.class)
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public BaseResponseBean badCredentialsException(BadCredentialsException ex, WebRequest request) {
BaseResponseBean responseBean = new BaseResponseBean();
StatusBean status = new StatusBean();
status.setEnErrorMessage(ex.getMessage());
status.setCode(403);
status.setSuccess(false);
responseBean.setStatus(status);
return responseBean;
}
@ExceptionHandler(HttpClientErrorException.BadRequest.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public BaseResponseBean badRequestException(HttpClientErrorException.BadRequest ex, WebRequest request) {
BaseResponseBean responseBean = new BaseResponseBean();
StatusBean status = new StatusBean();
status.setEnErrorMessage(ex.getMessage());
status.setCode(400);
status.setSuccess(false);
responseBean.setStatus(status);
return responseBean;
}
}
Upvotes: 0
Reputation: 2106
I solved the issue
(the custom exception handler must extend ResponseEntityExceptionHandler
).
My solution follows:
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
// paste custom hadling here
}
}
Upvotes: 41
Reputation: 446
In the controller class, I putted below method and it solved my issue. no need of controller advise or any other. Just overriding spring default exception with our user exception with body itself will solve the issue.
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = {MissingServletRequestParameterException.class})
ApiError handleMethodArgumentNotValid(MissingServletRequestParameterException ex) {
return new ApiError(ErrorCode.MISSING_REQUIRED_PARAMS, ex.getMessage());
}
Upvotes: 2
Reputation: 13080
override handleExceptionInternal
method handles all the exceptions, so you don't need to override each handle methods:
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
HttpStatus status, WebRequest request) {
MyErrorResponse myErrorResponse = new MyErrorResponse();
MyErrorResponse.setMessage(ex.getMessage());
return new ResponseEntity<>(myErrorResponse, status);
}
Upvotes: 0
Reputation: 13238
I have encountered the same issue, after spending lot of time debugging the issue I found the jackson is not properly deserializing the ErrorResponse
object.
This was because I didn't added the getters and setters for the field defined in the ErrorResponse
object. I was initializing the fields using constructor and there were no getters and setters defined for those fields.
SOLUTION:
So when I updated my ErrorResponse
object from
import com.fasterxml.jackson.annotation.JsonRootName;
import java.util.List;
@JsonRootName("error")
public class ErrorResponse {
private String message;
private List<String> details;
public ErrorResponse(String message, List<String> details) {
this.message = message;
this.details = details;
}
}
to the following one with getters and setters
import com.fasterxml.jackson.annotation.JsonRootName;
import java.util.List;
@JsonRootName("error")
public class ErrorResponse {
private String message;
private List<String> details;
public ErrorResponse(String message, List<String> details) {
this.message = message;
this.details = details;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List<String> getDetails() {
return details;
}
public void setDetails(List<String> details) {
this.details = details;
}
}
Jackson is now deserializing the ErrorResponse
properly and I'm getting the serialized body in the response.
Upvotes: 2
Reputation: 483
In my case, I need to handle all requests that have invalid parameters. So I extend my class with ResponseEntityExceptionHandler
and override the method handleMissingServletRequestParameter
. You can find your own handlers defined inside the class ResponseEntityExceptionHandler
@ControllerAdvice
public class YourExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity handleAllExceptions(Exception ex) {
// Log and return
}
@Override
public ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// Do your code here
return new ResponseEntity<>("YOUR REQUEST PARAMS NOT MATCH!");
}
}
Upvotes: 7
Reputation: 330
If You already have a class annotated with @ControllerAdvice
and don't want to create new one, You could use this piece of code:
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<?> handleMissingRequestBody(Exception ex) {
return handle(BAD_REQUEST, ex);
}
It should have the same behaviour as rvit34's solution.
Upvotes: 2
Reputation: 5701
I faced a similar issue and it didn't work for me because the component-scan
package provided didn't include the package where my @ControllerAdvice
was provided.
My XML had :
<context:component-scan base-package="com.bandi.rest" />
My package had a typo com.bandi.test.spring.exception
. After changing it to com.bandi.rest.spring.exception
it started working.
@ControllerAdvice
public class SpringRestExceptionHandler {
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public @ResponseBody ResponseEntity<ErrorResponse> handleNoMethodException(HttpServletRequest request,
NoHandlerFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse(ex);
errorResponse.setErrorMessage("resource not found with exception");
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Throwable.class)
public @ResponseBody ResponseEntity<ErrorResponse> handleDefaultException(Throwable ex) {
ErrorResponse errorResponse = new ErrorResponse(ex);
errorResponse.setErrorMessage("request has empty body or exception occured");
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
Also, if you need to handle scenario where your requested resource was not found (bad URL), then you'll have to add another configuration to your dispatcher servlet.
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
Complete Working code is available here
Upvotes: 2