I am making a simple rest service that makes some http calls and aggregates data using RestTemplate.
Sometimes i get NotFound error and sometimes BadRequest errors.
I want to respond with the same status code to my client and Spring seems to have this mapping out of the box. the message is okay but the Status code is always 500 Internal Server error.
I Would like to map my status code to the one i am initially receiving
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 500,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
i would like it to be this way
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 400,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
It throws HttpClientErrorException.BadRequest or HttpClientErrorException.NotFound
my code is a simple endpoint :
#GetMapping("/{id}")
public MyModel getInfo(#PathVariable String id){
return MyService.getInfo(id);
}
You can create global exception handling with #ControllerAdvice annotation. Like this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = YourExceptionTypes.class)
protected ResponseEntity<Object> handleBusinessException(RuntimeException exception, WebRequest request) {
return handleExceptionInternal(exception, exception.getMessage(), new HttpHeaders(), HttpStatus.NOT_ACCEPTABLE, request);
}
}
When an exception is thrown, the handler will catch and transform it to the desired response. The original exception wont be propagated.
The accepted solution with the #ControllerAdvice is insufficient. That surely marks the response with the custom status code for the exception. It does, however, not return the wanted response body as JSON but as only simple string - the message from the exception.
To get the correct status code and the default error body the DefaultErrorAttributes can help.
#ControllerAdvice
public class PackedTemplateNotRecodableExceptionControllerAdvice extends ResponseEntityExceptionHandler {
#Autowired
private DefaultErrorAttributes defaultErrorAttributes;
#ExceptionHandler(PackedTemplateNotRecodableException.class)
public ResponseEntity<Object> handlePackedTemplateNotRecodableException(final RuntimeException exception, final WebRequest webRequest) {
// build the default error response
webRequest.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.BAD_REQUEST.value(), RequestAttributes.SCOPE_REQUEST);
final Map<String, Object> errorAttributes = defaultErrorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults());
// return the error response with the specific response code
return handleExceptionInternal(exception, errorAttributes, new HttpHeaders(), HttpStatus.BAD_REQUEST, webRequest);
}
}
That way you'll receive the wanted error response, e.g. something like this:
{
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 400,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
I have spent a lot of time looking into this issue, including solutions from answers here, which didn't work for me (or I didn't implement correctly).
I finally got a breakthrough. Instead of throwing a generic Exception such as throw new Exception(message), I created classes that extends the Exception class for the specific exception type - with their respective HTTP error codes and message
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends Exception{
public BadRequestException(String message) {
super(message);
}
}
In your application logic, you can now throw the Bad Request exception with a message like so throw new BadRequestException("Invalid Email"). This will result in an exception thrown thus :
{
"timestamp": "2021-03-01T17:56:04.539+0000",
"status": 400,
"error": "Bad Request",
"message": "Invalid Email",
"path": "path to controller"
}
You can now create other custom exception classes for the different exceptions you want, following the above example and changing the value parameter in the #ResponseStatus, to match the desired response code you want. e.g for a NOT FOUND exception #ResponseStatus (value = HttpStatus.NOT_FOUND), Java provides the different HTTP status codes via the HttpStatus enum.
For more context
I hope this is detailed enough and helps someone :)
Possible duplicate of Spring Resttemplate exception handling Your code needs a controller advice to handle the exceptions from the service it is calling.
Related
I have a rest api in Java Spring Boot. I am making a POST request to api. If there is a data in the body it works fine but when no data is passed, it gives a 400 Bad Request response:
{
"timestamp": "2021-05-03T13:32:36.746+0000",
"status": 400,
"error": "Bad Request",
"message": "Required request body is missing: public org.springframework.http.ResponseEntity<com.package.MyResponse> com.package.MyController.getData(com.package.MyRequest)",
"path": "/api/"
}
How can I hide the package information and class name from the message in response? It should return mesage as:
"message": "Required request body is missing."
One option is to set #RequestBody(required=false) and then handle manually inside, but I want to avoid.
It would be great if someone can help. Thank you.
You can create a RestControllerAdvice extend it with ResponseEntityExceptionHandler. In implementation override corresponding method and send your custom message. In your case method will be handleHttpMessageNotReadable from ResponseEntityExceptionHandler.
Here is the sample code:
/**
* Intercept exception to response with error
*/
#RestControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
/**
* {#inheritDoc}
*/
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
// your custom implementation
}
}
I'd like to handle error response on spring boot.
here's my default error response for not found error.
{
"timestamp": "2020-12-09T02:37:03.698+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/v1.0/test"}
I tried to catch it on my custom error handle controller but doesn't work.
#ControllerAdvice
#RestController
#RequestMapping("${server.error.path:${error.path:/error}}")
#RequiredArgsConstructor
public class ExceptionController {
private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class);
private final BaseEntityVo baseEntityVo;
//uri not found
#ExceptionHandler({NoHandlerFoundException.class})
#ResponseStatus(HttpStatus.NOT_FOUND)
public BaseEntityVo notFoundException(HttpServletRequest httpServletRequest){
baseEntityVo.setError(ErrorConstant.SC_2007_URI_NOT_FOUND_ERROR.toString(httpServletRequest.getRequestURI()));
return baseEntityVo;
}
how to handle 404, 405, 415, 500 , and default error?
The default behaviour of the Spring DispatcherServlet is not to throw a NoHandlerFoundException where there is no handler found, it handles the response itself. You should be able to catch other exceptions with the above method but if you want to provide your own 404 responses you need to initialise the DispatcherServlet with throwExceptionIfNoHandlerFound.
I have a simple webservice that returns content either as json or as plain text (depending on the clients' accept http header).
Problem: if an error occurs during text/plain request, Spring somehow returns a 406 Not Acceptable. Which is kind of wrong, because spring could as well just write the error out as plain error text, and moreover should absolutely preserve the 400 error status:
#RestController
public class TestServlet {
#PostMapping(value = "/test", produces = {APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE, "text/csv"})
public Object post() {
throw new BadRequestException("bad req");
}
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String msg) {
super(msg);
}
}
POST request with accept=application/json:
{
"timestamp": "2018-07-30T14:26:02",
"status": 400,
"error": "Bad Request",
"message": "bad req",
"path": "/test"
}
BUT with accept=text/csv (or text/plain) shows an empty response with status 406 Not Acceptable.
I also noticed the DispatcherServlet.processDispatchResult() is called twice: first with my BadRequest exception, 2nd time with HttpMediaTypeNotAcceptableException. So clearly the rendering of my custom exception fails, but why?
The problem is the restrictive Accept header allowing only one content type as response. In case of an error, Spring MVC needs to handle the BadRequestException and produce the required content type using a registered HttpMessageConverter.
By default Spring Boot has no message converter to produce text/plain directly from any object. You may register an ObjectToStringHttpMessageConverter (as a bean should work for Spring Boot) to allow this and you will get the result of BadRequestException.toString() as response body.
I assume a similar problem for text/csv but I am not sure how your setup for CSV message conversion looks like.
The condition written in "produces" determines the media type to use for the response to be "text/csv". So For a success scenario it works fine, **
but when you go for rendering an exception with a JSON body that
becomes a problem and gives you a 406 instead.
**
in latest versions of spring framework the problem fixes, but in old versions,as mentioned in Spring JIRA comments you should remove HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribute from request
the code might be like this :
#RestControllerAdvice
public class ExampleControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<?> handleException(HttpServletRequest request, Exception e) {
request.removeAttribute(
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return new ResponseEntity<?>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
we can handle exception in advice
#ControllerAdvice
class ExceptionHandler{
#ExceptionHandler(value = {HttpMediaTypeNotAcceptableException.class})
public ResponseEntity handleMediaTypeException(HttpMediaTypeNotAcceptableException e) {
APIErrorResponse apiErrorResponse = new APIErrorResponse();
apiErrorResponse.setErrorCode("set custom code here");
apiErrorResponse.setErrorMessage("set custom meggage here/ here we can use message from object of exception i.e e.getMessage()");
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
I have the following controller advice to handle the exceptions within my app globally:
#ControllerAdvice
public class ExceptionHandlingController {
// Convert a predefined exception to an HTTP Status code
#ResponseStatus(value=HttpStatus.BAD_REQUEST) // 400
#ExceptionHandler(ConstraintViolationException.class)
public void ConstraintViolationExceptionHandler() {
//Nothing to do
}
}
The code below is the controller, which tries to save an object to the db (in the service layer). The class that object belongs to, has annotations that fail.
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public void create(#RequestBody CustomUserDetails user, HttpServletResponse response) {
logger.debug("User signup attempt with username: " + username);
userDetailsServices.saveIfNotExists(user);
}
I expect the client to receive a 400 response when ConstraintViolationException is thrown.
When the method returns void , no response is returned. When I change it String and return a random text, I get 404 response back.
{
"timestamp": 1495489172770,
"status": 404,
"error": "Not Found",
"exception": "javax.validation.ConstraintViolationException",
"message": "Validation failed for classes [security.model.CustomUserDetails] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='must match \"^(?!.*\\..*\\..*)[A-Za-z]([A-Za-z0-9.]*[A-Za-z0-9]){8,15}$\"', propertyPath=username, rootBeanClass=class com.boot.cut_costs.security.model.CustomUserDetails, messageTemplate='{javax.validation.constraints.Pattern.message}'}\n]",
"path": "/signup"
}
How can I make this return a simple BAD REQUEST message as it is defined for the #ExceptionHandler.
Note: ConstraintViolationExceptionHandler is hit!
After reading some blog posts about making a custom exception handler for Spring, I wrote the following class:
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<Object> exceptionHandler(Exception e) {
HashMap<String, Object> msg = new HashMap<>(2);
msg.put("error", HttpStatus.PRECONDITION_FAILED.value());
msg.put("message", "Something went wrong");
return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
}
}
The intent is to send msg in the JSON response instead of giving away the Spring exception what was thrown for whatever reason.
This class isn't working, however.
When I hit, say, and invalid endpoint for my server API, I get the default response payload:
{
"timestamp": 1449238700342,
"status": 405,
"error": "Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message": "Request method 'POST' not supported",
"path": "/bad_enpoint"
}
What am I missing?
Thanks.
Your handler will not be called because you want to map Exception to your custom error response but Spring MVC most likely already has one exception handler registered for Exception class. It also has one that handles HttpRequestMethodNotSupportedException for sure.
It is not a great idea however, to overwrite entire Spring MVC exception handling/mapping anyway. You should only care about specific exceptions - ones that you define.
Please read this article for a bit more insight into Spring MVC exception handling.
You don't need to extend ResponseEntityExceptionHandler to make it work.
Setting two HttpStatuses is reaaaaaly bad idea.
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<String> exceptionHandler(Exception e) {
return new ResponseEntity<>("Something went wrong", HttpStatus.BAD_REQUEST);
}
}