Spring boot + REST exception Handler - always get 500 error - spring

I have a REST service which could throw an exception.
This is my custom exception
public class CommentNotPostableException extends Exception {
public CommentNotPostableException(final String message) {
super(message);
}
}
Then, for my REST Api, I implemented a RestResponseEntityExceptionHandler which extends ResponseEntityExceptionHandler
One of its methods is
#ExceptionHandler(value = { CommentNotPostableException.class })
protected ResponseEntity<Object> handleCommentNotPostableException(CommentNotPostableException ex, WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getMessage());
ApiError apiError = new ApiError(HttpStatus.valueOf(46),
ex.getLocalizedMessage(), builder.substring(0, builder.length()));
logger.error("Already posted", ex);
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
which should get the exception...
Now my controller is (snippet)
public ResponseEntity<?> postComment(#Valid #RequestBody CommentDTO dto, Errors errors) throws CommentNotPostableException{
.....
if(service.hasAlreadyPosted(user, reservation)){
throw new CommentNotPostableException("Already posted");
}
....
}
So, when hitting the exception i should recevive an error 46, instead i'm getting a 500 error, even if my custom exception is taken into account... Is there some kind of ordering in exceptions?
{
"timestamp": 1496084392755,
"status": 500,
"error": "Internal Server Error",
"exception": "it.besmart.easyparking.exceptions.CommentNotPostableException",
"message": "Already posted",
"path": "/api/oauth/comment/new"
}
this is what i get from logs
2017-05-29 21:13:32 DEBUG i.b.e.w.r.CommentRestController[57] - dto รจ CommentDTO [comment=A, vote=3, reservationId=7161]
2017-05-29 21:13:32 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet][181] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is it.besmart.easyparking.exceptions.CommentNotPostableException: Already posted] with root cause
it.besmart.easyparking.exceptions.CommentNotPostableException: Already posted
at it.besmart.easyparking.web.restcontroller.CommentRestController.postComment(CommentRestController.java:78) ~[classes/:na]

Related

Migrating from Jersey client to RestTemplate, but it catch 401 error as HttpClientErrorException but Jersey client was not throwing this error why?

In my Service there was Jersey client implementation to call a rest API now I was migrating this code to RestTemplate.
In old code when there was a 401 error that comes as a response from the backend and I store the response in an object.
But when I migrated the code to RestTeplate the 401 is caught by HttpClientErrorException class so I am not able to get the response since the code flow goes to the catch block.
Jersey Client code
public Employees getEmployees1() throws MyException {
Employee employee=new Employee(23, "Test", "Test", "Test#test.com");
ClientResponse response=null;
try {
Client client = Client.create();
WebResource webResource = client.resource("http://localhost:8080/employees/");
response = webResource.accept("application/json")
.type("application/json").header("Authorization", "invalid Data").post(ClientResponse.class, employee);
}catch (RuntimeException e) {
logger.error("Runtime Error Occured -{} Response - {} ",e.getMessage(),response.getStatus());
throw new MyException("Unexpected Error Occured",e);
}catch (Exception e) {
logger.error("Some Error Occured -{} Response - {} ",e.getMessage(),response.getStatus());
throw new MyException("Unexpected Error Occured",e);
}
return response.readEntity(Employees.class);
}
RestTemplate Code
public Employees getEmployees() throws MyException {
Employee employee=new Employee(23, "Test", "Test", "Test#test.com");
HttpHeaders headers=new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add(HttpHeaders.AUTHORIZATION, "invalid Data");
ResponseEntity<Employees> response=null;
try {
response = this.restTemplate.exchange("http://localhost:8080/employees/", HttpMethod.POST, new HttpEntity<Employee>(employee,headers), Employees.class);
}catch (RuntimeException e) {
logger.error("Runtime Error Occured -{} Response - {} ",e.getMessage(),response.getStatusCode());
throw new MyException("Unexpected Error Occured",e);
}catch (Exception e) {
logger.error("Some Error Occured -{} Response - {} ",e.getMessage(),response.getStatusCode());
throw new MyException("Unexpected Error Occured",e);
}
return response.getBody();
}
By default RestTemplate throws an HttpStatusCodeException (a child of it) for all 400+ status codes - see DefaultResponseErrorHandler. You can change this behavior by setting your own implementation of ResponseErrorHandler to RestTemplate using setErrorHandler or if RestTemplate is constructed using RestTemplateBuilder - using errorHandler method of the builder.
I used the default ResponseErrorHandler, by using this it will bypass all the ResponseError exception
RestTemplate rt = restTemplateBuilder.errorHandler(new ResponseErrorHandler(){
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}})
.build();

Webclient onStatus does not work in case of 406 returned from downstream API

I'm doing a onStatus implementation in my API when I use a webclient (Webflux) to call external API:
//Webclient Call
Flux<Movie> movies = webclient.get().uri(uriBuilder -> uriBuilder.path(api_url)
.build(author))
.retrieve()
.onStatus(HttpStatus::is4xxClientError,
response -> Mono.error(new AcceptHeaderNotsupportedException(response.statusCode().getReasonPhrase())))
.bodyToFlux(Movie.class)
//Global Handler Exception Class
public class GlobalExceptionHandler {
#ExceptionHandler(AcceptHeaderNotsupportedException.class)
public ResponseEntity<?> AcceptHeaderHandling(AcceptHeaderNotsupportedException exception){
ApiException apiException = new ApiException(HttpStatus.NOT_FOUND.value(), exception.getMessage());
return new ResponseEntity<>(ApiException, HttpStatus.NOT_FOUND);
}
}
//AcceptHeaderNotsupportedException Class
public class AcceptHeaderNotsupportedException extends RuntimeException{
public AcceptHeaderNotsupportedException(String message){
super(message);
}
}
//Api custom Exception
public class ApiCustomException{
private int code;
private String message;
}
I am testing a scenario webclient call that return a 406 error from downstream api. So i want to map the response to my object representation and give to my client (postman in this case).
{
code: 406,
"message": error from downstream api
}
but i am getting to client
{
"timestamp": "2021-08-29T14:31:00.944+00:00",
"path": "path",
"status": 406,
"error": "Not Acceptable",
"message": "Could not find acceptable representation",
"requestId": "ba66698f-1",
"trace": "org.springframework.web.server.NotAcceptableStatusException: 406 NOT_ACCEPTABLE \"Could not find acceptable representation\"\n\tat ....}
In case of a 404 error from downstream API the mapping response works fine.
{
code: 404,
"message": not found
}
My question is if i am doing .onStatus(HttpStatus::is4xxClientError should not work for both (404, 406 or other responde code with 4xx ?

RestTemplate including body in exception

Suppose I call a webservice with RestTemplate and it returns 500 status error with this body:
{
"timestamp": "2019-10-10T16:51:15Z",
"status": 500,
"error": "Internal Server Error",
"message": "Error occurred while retrieving entity with id: bb00b45c-9e17-4d75-a89a",
"path": "/api/service"
}
Currently RestTemplate exception message is something like:
Caused by: org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 null
at org.springframework.web.client.HttpServerErrorException.create(HttpServerErrorException.java:79)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:124)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:102)
Is there any way to include the body of the response in the RestTemplate exception message without using a custom error handler?
Thanks!
May be something like this (without a Custom Error Handler)
ObjectMapper mapper;
try {
ResponseEntity<User> response = restTemplate.exchange(url,
HttpMethod.POST, requestEntity, User.class);
} catch (HttpStatusCodeException e) {
List<String> header = e.getResponseHeaders().get("x-app-err-id");
String errorMessageId = "";
if (header != null && !header.isEmpty()) {
errorMessageId = header.get(0);
}
// You can get the body, but deserialise it using mapper into a POJO
ErrorResponseBody errorResponseBody = mapper.readValue(e.getResponseBodyAsString(),
ErrorResponseBody.class);
// You can re-throw it if you want or use the response body
throw new CustomException(e, HttpStatus.INTERNAL_SERVER_ERROR);
}

Spring Exception Handler log all exceptions but return original response

So I am trying to log all uncaught exceptions returned by the controllers of a spring project in a generic fashion.
I was able to do this with the following exception handler:
#ControllerAdvice
public class ControllerConfig {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void handleBadRequest(HttpMessageNotReadableException e) {
logger.warn("Returning HTTP 400 Bad Request", e);
throw e;
}
#ExceptionHandler(AccessDeniedException.class)
public void defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception {
logger.error("Error in request:" + request.getRequestURL(), e);
throw e;
}
This also returns the error responses of the request, so I don't have to differentiate between all the different error response codes.
However, for every invocation of the method a second error log is created because of the exception thrown in the method:
Code is from org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking #ExceptionHandler method: " + exceptionHandlerMethod);
}
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception);
}
catch (Exception invocationEx) {
if (logger.isErrorEnabled()) {
logger.error("Failed to invoke #ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
}
return null;
}
So is there a smarter way to return the original exception of the method?
It depends on what do you mean by "a smarter way to return the original exception". What exactly would you like to return to the client? If this is just the message of the exception you can simply return it from the exception handler and annotate the method with #ResponseBody. Spring will do the rest for you.
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleBadRequest(HttpMessageNotReadableException e) {
logger.warn("Returning HTTP 400 Bad Request", e);
throw e.getMessage();
}
You can also return some custom object which wraps the exception information and any other data that you desire.

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

Resources