3

When throwing an exception from a spring-boot controller, the message in the server response is empty - but only if I don't run locally. That last part is what's confusing me the most. I mean, it would make perfect sense to be able to have spring-boot remove parts from the error response. Like the stacktrace for example, noone wants to send that out except when debugging.

But when I run the application locally, I get the full error response, message, stacktrace and all (even when not running in debug mode, which I first suspected might be the reason for this). A typical error might look something like this:

{
    "timestamp": "2020-10-14T09:46:35.784+00:00",
    "status": 400,
    "error": "Bad Request",
    "trace": "webcam.yellow.service.controller.error.BadRequestException: New password must be different from old password! at /*REDACTED*/",
    "message": "New password must be different from old password!",
    "path": "/users/9"
}

But when I produce the same error on a deployed server, all I get is this:

{
    "timestamp": "2020-10-14T09:29:57.720+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "",
    "path": "/users/9"
}

I don't mind the stacktrace being removed at all (in fact I want it to be removed), but I would really like to receive that error message.

One thought I had was that it might be related to cross-origin access, but I get the same behaviour when producing the error through swagger instead of our frontend, and swagger is same-origin.

I would fully expect such behaviour to be configurable in spring-boot, that would be convenient. Trouble is, I'm not configuring it. I compared the configuration properties of the running server to my local ones and I don't see any property that might be responsible for that. Nor can I find any if I google it. According to all the tutorials I find, this should work just fine. Which it kind of does, except not on the running servers.

Does anybody know what in spring-boot is causing this behaviour and how to configure it? Using spring-boot 2.3.3 by the way.

Additional information:

After some fooling around, I managed to reproduce the problem locally. I get the shortened error response if I build the application, and then run it from the command line directly with java -jar. Running gradle bootRun results in the server returning the full error message.

I've tried to return my own error response through a ControllerAdvice:

@ControllerAdvice
class BadRequestHandler : ResponseEntityExceptionHandler() {

    @ExceptionHandler(value = [BadRequestException::class])
    protected fun handleBadRequest(ex: BadRequestException, request: WebRequest): ResponseEntity<Any> {
        val message = ex.message
        return ResponseEntity(message, HttpHeaders(), HttpStatus.BAD_REQUEST)
    }
}

This was just intended to be a quick test to see if I could change the server response. Turns out I can't, the client still gets the same response, although the handler is executed. So whatever takes the information out of the request must come further down the chain.

Does anybody have any idea what's happening here??

UncleBob
  • 1,233
  • 3
  • 15
  • 33

2 Answers2

4

This is intended behavior since Spring Boot 2.3 as explained here

Setting server.error.include-message=always in the application.properties resolves this issue.

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
MarK
  • 56
  • 3
1

The response can be configured by injecting a custom ErrorController, like for example this one:

@Controller
class ExampleErrorController(private val errorAttributes: ErrorAttributes) : ErrorController {

    private val mapper = ObjectMapper()

    @RequestMapping("/error")
    @ResponseBody
    fun handleError(request: HttpServletRequest): String {
        val webRequest = ServletWebRequest(request)
        val error = errorAttributes.getError(ServletWebRequest(request))

        // if it's not a 500, include the error message in the response. If it's a 500, better not...
        val errorAttributeOptions = if (error !is HttpServerErrorException.InternalServerError) {
            ErrorAttributeOptions.defaults().including(ErrorAttributeOptions.Include.MESSAGE)
        } else ErrorAttributeOptions.defaults()

        val errorDetails = errorAttributes.getErrorAttributes(
                webRequest, errorAttributeOptions)

        return mapper.writeValueAsString(errorDetails)
    }

    override fun getErrorPath(): String = "/error"
}

Note that this takes ErrorAttributeOptions.defaults() as a baseline, then configures what goes in. It appears that this default object is the one used by the default ErrorController spring boot provides, and it is in fact this object that is different depending on whether I run this from gradle/intelij directly or build it into a jar. Why I couldn't find out, but I verified and confirmed the behaviour. I assume it is intended, albeit not widely documented.

Once I learned this, I wondered why it wasn't possible to just configure the default Options object globally for an application rather than providing an entire controller, which in many instances would be sufficient, but it does not look like that's possible at this point.

UncleBob
  • 1,233
  • 3
  • 15
  • 33