The spring-boot produces very convinient default JSON response for exceptions, like this.

{
  "timestamp" : "2016-11-06T03:16:02.440+0000",
  "status" : 500,
  "error" : "Internal Server Error",
  "exception" : "demo.MyException",
  "message" : "bla bla",
  "path" : "/api/data"
}

When I write my exception handler like this:

    @ExceptionHandler
    @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "bla bla")
    public void handleMyException(MyException e) {
    }

Then HTTP response includes spring-boot's JSON with error details. As expected.

But If I remove the reason annotation argument to make use of exception message, like this:

    @ExceptionHandler
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public void handleMyException(MyException e) {
    }

Then HTTP response is no more includes spring-boot's JSON with error details. It has empty body instead.

Maybe it is related to #5774.

Comment From: bclozel

The body of your second handleMyException variant is empty and I see nowhere a response body being set. Spring Boot does help with nice error messages when it can, but in this case you're explicitly handling the error and not writing any body.

In my opinion, this is the expected behavior.

Could you elaborate a bit more on why you think there should be a response body here?

Comment From: xak2000

The body of my first handleMyException variant is also empty. The only difference is the reason attribute of @ResponseStatus annotation.

So why it is inconsistent behaviour?

I use the void variant of @ExceptionHandler method and without HttpServletResponse argument, so I expect that my code just change the response status, but not the default spring-boot's JSON response.

If I add the @ResponseStatus(code = HttpStatus.NOT_FOUND) annotation (even without reason) to the exception class, then spring-boot still sends it's JSON. But what should I do if I can't add this annotation to the exception class (for example, the exception is on another application layer which should not know about web layer)? The @ExceptionHandler is for this, right? But how can I change just response status, without changing default JSON response?

The workaround now is:

    @ExceptionHandler
    public void handleMyException(MyException e, HttpServletResponse res) throws IOException {
        res.sendError(HttpStatus.NOT_FOUND.value());
    }

This code just changes the status, but still sends default spring-boot JSON with exception's error message.

But my main point is still: why it is inconsistent behaviour when we use @ExceptionHandler in combination with @ResponseStatus, depending on whether the reason attribute is set or not?

The @ResponseStatus's documentation says:

Warning: when using this annotation on an exception class, or when setting the reason attribute of this annotation, the HttpServletResponse.sendError method will be used.

But why the HttpServletResponse.sendError method not used when reason is not set? If it is to allow the default container error page to show, then why not allow this page to show when the reason is set? Why the reason attribute controls whether we send error or just set the response status?

I don't see why this inconsistency exists.

Comment From: bclozel

Indeed, something may not be right here, let me look a bit into this.

Just a few pointers to add to the discussion: - Spring MVC's ResponseStatusExceptionResolver does use sendError in all cases (with or without the reason attribute) - This looks a lot like #3623, although that one is now covered by unit tests

Comment From: bclozel

When added on an Exception class, @ResponseStatus(status) and @ResponseStatus(status, reason) will both trigger an error dispatch to the container and will be handled by Boot's ErrorController. In this case, the ResponseStatusExceptionResolver is involved and we know that the response should represent the error itself.

When added on an @ExceptionHandler annotated method, @ResponseStatus(status, reason) will trigger an error dispatch to the container and will be handled by Boot's ErrorController, but @ResponseStatus(status) won't. Note that in fact, this is the same as adding a @ResponseStatus on any Controller method; the same code path is involved, in ServletInvocableHandlerMethod.

In those cases, we assume that providing a reason means that the handler is returning an error and that this should be dispatched as an error in the container. If reason is not there, then we can't assume that the provided response should be dispatched as an error to the container: - the response status could be 2xx or 3xx - the handler could return a custom error object to be serialized as the HTTP response body and the Error Controller should not be involved there

It does feel inconsistent but at the moment I can't find a proper solution that would not break many web apps out there with perfectly valid code.

Your comments already list the two available workarounds for this: - use the reason attribute - use the HttpServletResponsedirectly with res.sendError(HttpStatus.NOT_FOUND.value());

Comment From: xak2000

If reason is not there, then we can't assume that the provided response should be dispatched as an error to the container

Can't we decide this based on presense of @ExceptionHandler on the same method? So, if it is normal Controller method, then @ResponseStatus without reason should just change the response status, but if if is @ExceptionHandler method then it should sendError.

At least it is more consistent: @ResponseStatus on exception handlers always behave the same regardless of params. But it is backward incompatible changes of course. Maybe someone relies on behaviour of @ResponseStatus(status) (without reason) on @ExceptionHandler and handles their exceptions returning 200 OK. :)

Comment From: quaff

FYI, I created a relation issue https://github.com/spring-projects/spring-framework/issues/27156