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??
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.
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.
Related
So I have a controller with one GET method. I need to test it. When I write in URL request with incorrect iso code of the country, it throws me back a custom exception. So how can I test it?
So here 'UA' is incorrect argument
#Test
fun check_for_incorrect_iso_code() {
mockMvc.perform(get("/countries/UA"))
.andDo(print())
.andExpect(status().is4xxClientError)
}
Test is working, but I need to extend it and check if it throws my custom exception - 'InvalidIsoCodeException' for example.
Thanks for the answer.
Your Java code is throwing an InvalidIsoCodeException but your server/controller cannot throw exceptions. Instead it sends back an HTTP response to the client. The InvalidIsoCodeException is mapped by Spring to a specific response. You're already checking the status of the response with .andExpect(status().is4xxClientError()). You can also verify the body of the response if you want to be more specific.
If you want to test for the exception then you have to test your controller like a normal Java class without MockMVC.
My service is currently experiencing 415 errors, but I'm not getting any logs to find out what errors they are.
Will I be able to add logging in some kind of filter so that I can know what's going on?
#RequiresHttps
#RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity<String> doAction(#NonNull #RequestBody CustomRequest[] requests) {
It looks like the 415 is happening inside spring mvc engine and it doesn't event reach your controller so that you can't really place the logs in our code (in doAction method for example).
Try to enable tomcat embedded access logs and you'll see the file with all the requests and return status. These are disabled by default so you should add the following into application.properties or yaml:
server.tomcat.accesslog.enabled=true
There are some configurations / customizations you can do with that, you can read about them in this tutorial for example
I am having a hard time in Chaos Monkey For Spring Boot regarding error responses when a user POSTs an invalid (like {"level": -2}update via REST to our actuator endpoint where one can update options of the behavior of CMSB (only positive levels are allowed). In the first image, I set the management.server.port to 8888 and the app port to 8080. When posting a new property to the CMSB REST API I am getting the following response (which is not what we would have expected):
And in case I leave the management port at the same port the same as the app I am getting the following response:
For both cases we would have expected the same error response (the second one). So we're asking us (at CMSB) whether this is an intended behavior of spring boot and if not, what our options are to get around writing our own error response handler in case the management port is different from the app port.
Please note that this is not about the intended behavior of chaos monkey for spring boot but rather about whether this is a spring boot bug or not. In both cases we would like to have a detailed error response so a user knows what's wrong. Under the hood we are using the #Validated annotation in combination with something like this to validate inputs:
#Data
#NoArgsConstructor
#Validated
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AssaultPropertiesUpdate {
#Nullable
#Min(value = 1)
#Max(value = 10000)
private Integer level;
On a side note: in both cases the error message in the logs is correct. But only in the second case is this error message
WARN 4477 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> de.codecentric.spring.boot.chaos.monkey.endpoints.ChaosMonkeyRestEndpoint.updateAssaultProperties(de.codecentric.spring.boot.chaos.monkey.endpoints.AssaultPropertiesUpdate): [Field error in object 'assaultPropertiesUpdate' on field 'level': rejected value [-2]; codes [Min.assaultPropertiesUpdate.level,Min.level,Min.java.lang.Integer,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [assaultPropertiesUpdate.level,level]; arguments []; default message [level],1]; default message [must be greater than or equal to 1]] ]
used as the response payload.
Minimal example project: https://github.com/fletchgqc/mediator
Start the project with mvn spring-boot:run. and then do a POST against http://localhost:8080/actuator/chaosmonkey/assaults with the the payload: {"level": -2}. Correct error response should be shown (like in image 2).
Then stop the project, to https://github.com/fletchgqc/mediator/blob/master/src/main/resources/application.properties add management.server.port=8888 and start the app again. Do a POST against http://localhost:8888/actuator/chaosmonkey/assaults with the same payload as before. The wrong error message should appear (like in image 1).
Looks like the spring team fixed it here: https://github.com/spring-projects/spring-boot/issues/21036
A client software is trying to access my Spring-MVC rest server, but it's getting a 400 (Bad Request) response every time. I know my server is fine (it's in use by many other clients), but I cannot debug the client application, so I cannot see what it is sending.
Is there a way for me to see what JSON I am receiving before Spring tries to convert it to an entity and fails? It's okay if I can only do this at debug time, I just need to be able to give support to this application's creators.
Just in case, here is the spring-mvc controller method:
#Named
#RequestMapping(value = "/taskmanager/task")
public class TaskManagerTaskRest {
#RequestMapping(value = "", method = RequestMethod.POST)
#ResponseBody
public void createTask(#RequestBody Task task, HttpServletRequest request,
HttpServletResponse response) throws CalabacinException {
// This code never gets executed because the Task json is invalid, but I don't know how I could see it.
...
...
}
}
Try to use Fiddler. It will help you to catch HTTP requests/responses. You will be able to see your JSON.
You can create and use a AbstractRequestLoggingFilter filter implementation and conditionally log the relevant parts of the request. You should use ContentCachingRequestWrapper to wrap the request.
I'm following the documentation (State Machine Error Handling) to implement error handling. However, when an exception occurs it is propagated up rather than intercepted. I tried using the interceptor, the listener and the #OnStateMachineError without any success. Debugging the code, neither MethodInvokingStateMachineRuntimeProcessor.java:52 or any of its callers have any specific logic to handle errors.
Replicating the issue is simple, just create a state machine (I'm using the latest snapshot) and register the bean:
#WithStateMachine
public class ExceptionThrowingAction {
#OnTransition
public void throwError(#EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
throw new RuntimeException("test error");
}
}
Am I missing something or is it a genuine bug? If so, I'll raise as an issue
Yes, this is a bug. We've done a lot of changes in master to harden there user level hooks. None of those should break machine execution. Please raise an issue and we'll fix it.