I believe there is a regression between Spring Boot 2.2.4 and 2.2.5 in handling bean validation.
I have a small project reproducing it: https://github.com/pkubowicz/spring-validation-bug
In short:
- UserDto has @NotBlank
field called id
, additionally the constructor does a null-check
- I am running MockMVC tests
- passing an empty string for id field correctly returns HTTP 400
- not passing id field causes HTTP 500 while it used to return HTTP 400 (first problem)
- not passing id causes an exception breaking the MVC test - you cannot write andExpect(status().isBadRequest())
(second problem)
I think apart from fixing validation behaviour, Mock MVC should be fixed so that calling mvc.perform()
never throws an exception and you can just write a test asserting on MVC result.
Stacktrace:
Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: JSON conversion problem: Cannot construct instance of `com.example.UserDto`, problem: `java.lang.NullPointerException`; nested exception is com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `com.example.UserDto`, problem: `java.lang.NullPointerException`
at [Source: (PushbackInputStream); line: 1, column: 15]
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: JSON conversion problem: Cannot construct instance of `com.example.UserDto`, problem: `java.lang.NullPointerException`; nested exception is com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `com.example.UserDto`, problem: `java.lang.NullPointerException`
at [Source: (PushbackInputStream); line: 1, column: 15]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:183)
at com.example.UserControllerTest.returns400ForMissingId(UserControllerTest.java:26)
Curl:
% curl http://localhost:8080/users -H 'Content-Type: application/json' -XPOST --data '{"name": "Joe"}'
{"timestamp":"2020-03-02T10:05:13.085+0000","status":500,"error":"Internal Server Error","message":"JSON conversion problem: Cannot construct instance of `com.example.UserDto`, problem: `java.lang.NullPointerException`; nested exception is com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `com.example.UserDto`, problem: `java.lang.NullPointerException`\n at [Source: (PushbackInputStream); line: 1, column: 15]","path":"/users"}
% curl http://localhost:8080/users -H 'Content-Type: application/json' -XPOST --data '{"id": "", "name": "Joe"}'
{"timestamp":"2020-03-02T10:05:48.100+0000","status":400,"error":"Bad Request","errors":[{"codes":["NotBlank.userDto.id","NotBlank.id","NotBlank.java.lang.String","NotBlank"],"arguments":[{"codes":["userDto.id","id"],"arguments":null,"defaultMessage":"id","code":"id"}],"defaultMessage":"must not be blank","objectName":"userDto","field":"id","rejectedValue":"","bindingFailure":false,"code":"NotBlank"}],"message":"Validation failed for object='userDto'. Error count: 1","path":"/users"}
Comment From: bclozel
See spring-projects/spring-framework#24455 and spring-projects/spring-framework#24610
Comment From: rstoyanchev
I believe this issue can be closed as duplicate of spring-projects/spring-framework#24610.
As for having mvc.perform()
never throw an exception, that would break existing tests that happen to rely on the current behavior. Also for tests that try to assert the response, that actually run into unhandled errors, would now see response assertion failures (e.g. missing headers) instead of the actual unhandled error. If you really want to do this, you can insert a test Filter
and use try-catch before delegating.
Comment From: wilkinsona
Thanks, @rstoyanchev.