No @ResponseBody returned from @ExceptionHandler in Spring boot app deployed in Tomcat
Asked Answered
F

1

4

I have a Spring Boot web app that runs just fine from STS but shows different behavior when running in Tomcat from a WAR file.

I use Thymeleaf to handle all my web pages but I have a couple pages that are using jQuery to send async calls and make user experience more dynamic.

Anyway, I have a Controller method that calls a service method which may throw a RuntimeException which I handle this way :

@ExceptionHandler(MyRuntimeException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public @ResponseBody String handleMyRuntimeException(MyRuntimeException exception) {
    return "Oops an error happened : " + exception.getMessage();
}

In JS, I use the response body I return above to display a message on screen.

That works perfectly fine when running my app in STS but once I switch to deploy it in Tomcat the ErrorPageFilter gets invoked and in doFilter() it does:

if (status >= 400) {
    handleErrorStatus(request, response, status, wrapped.getMessage());
    response.flushBuffer();
}

In handleErrorStatus() it creates an error with the status and associated message but doesn't return my response.

I haven't figured out how to solve this and I'd really appreciate if anybody could help.

Thanks!

Fusain answered 16/3, 2015 at 10:59 Comment(1)
For now, my workaround is to catch the Exception thrown by my service and return a JSON object with a SUCCESS or FAILURE status and corresponding message and / or result data.Fusain
F
3

I went around this issue (I would think that is a Spring Boot issue) by doing the following.

  1. Separate Rest and Mvc controllers See my question here : Spring MVC: Get i18n message for reason in @RequestStatus on a @ExceptionHandler

  2. Inject Jackson converter and write response myself :

    @ControllerAdvice(annotations = RestController.class)
    @Priority(1)
    @ResponseBody
    public class RestControllerAdvice {
        @Autowired
        private MappingJackson2HttpMessageConverter jacksonMessageConverter;
    
        @ExceptionHandler(RuntimeException.class)
        @ResponseStatus(value = HttpStatus.BAD_REQUEST)
        public void handleRuntimeException(HttpServletRequest request, HttpServletResponse response, RuntimeException exception) {
            try {
                jacksonMessageConverter.write(new MyRestResult(translateMessage(exception)), MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
                response.flushBuffer(); // Flush to commit the response
                } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
Fusain answered 27/3, 2015 at 8:33 Comment(6)
It is important to call the flushBuffer() method or this won't work.Fusain
What has really made a difference is that you've removed @ResponseStatus (see jira.spring.io/browse/SPR-8251). Instead you can simply return a ResponseEntity. There is no need to write yourself through Jackson.Bradway
I'll try that thanks ! But if if I return HttpStatus.BAD_REQUEST in my ResponseEntity, wouldn't I get the same behavior ?Fusain
Returning a new ResponseEntity<MyRestResult>(myRestResult, HttpStatus.BAD_REQUEST) produces the same result. I'll log a Boot issue. Thanks for your time.Fusain
@DamienPolegato Thanks so much for your code. My @RestControllerAdvice annotated global exception-handler would correctly pass a ResponseEntity to the client as JSON, but it wouldn't pass it to client on a handler for @ExceptionHandler(value = ConstraintViolationException.class). You're solution let's me give a personlized Repsonse for constraints back to the client.Famine
Couldn't get it to work with a ResponseEntity, but simply writing the response directly to HttpServletResponse made it work. So thanks for that.Aulea

© 2022 - 2024 — McMap. All rights reserved.