Spring - Jackson request mapping with validation. Throws "HttpMessageNotReadableException" when string given for long property

binding, data-binding, mapping, spring, validation

Solution

It's important to understand that first Jackson deserializes the JSON content of the request body and creates a UserAgreement object. Only then can standard bean validation be applied as a result of the @Valid annotation. So you can think of @Valid as an added layer of validation only after JSON deserialization succeeds. Use an @ExceptionHandler method for deserialization errors.

Problem

I'm trying to use @RequestBody @Valid annotations to bean validation and data mapping in Spring. As you can see in entity bean, I'm using @NotNull and @Min annotations, both of them are using different validation message and I can read this messages in controller at BindingResult argument. But, when client sent String value instead of long for "operatorId" field, I'm getting an error you can see it bellow. My question is : When client sets a non-numeric string value for operatorId, I want to return an error message text to client. (ex: "You should sent a numeric value etc.") Entity Bean : ``` @Entity(name="ua") @Table(name="ua") public class UserAgreement extends BaseContractEntity { @Column(name = "OPERATOR_ID") @NotNull(message = "operatorId must be provided") @Min(value = 1, message = "operatorId must be bigger then zero") protected long operatorId; //... getters setters, etc } ``` Controller : ``` @RequestMapping(value = "/ug", method = RequestMethod.POST) @ResponseBody public ServiceResponse saveUserAgreement(@RequestBody @Valid UserAgreement userAgreement, BindingResult result) { ServiceResponse serviceResponse = new ServiceResponse(); if(result.hasErrors()) { serviceResponse.setMessage(result.getAllErrors().get(0).getDefaultMessage()); serviceResponse.setErrorCode("400"); return serviceResponse; } else { // call service serviceResponse.setMessage("ok"); return serviceResponse; } } ``` This is my example request : When make a POST request with this json text (wrong operatorId non-numeric value !) : ``` {"content":"text content text","announcement":"YES","approved":"YES","operatorId":"5fg0"} ``` Getting this error : ``` org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not construct instance of long from String value '5fg0': not a valid Long value at [Source: org.apache.catalina.connector.CoyoteInputStream@9ba3afe; line: 1, column: 69] (through reference chain: com.ggg.agr.core.model.UserAgreement["operatorId"]); nested exception is org.codehaus.jackson.map.JsonMappingException: Can not construct instance of long from String value '5fg0': not a valid Long value at [Source: org.apache.catalina.connector.CoyoteInputStream@9ba3afe; line: 1, column: 69] (through reference chain: com.ggg.agr.core.model.UserAgreement["operatorId"]) at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readJavaType(MappingJacksonHttpMessageConverter.java:169) at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.read(MappingJacksonHttpMessageConverter.java:161) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:135) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:180) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:95) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:123) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838) at javax.servlet.http.HttpServlet.service(HttpServlet.java:647) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812) at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) at java.lang.Thread.run(Thread.java:680) Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of long from String value '5fg0': not a valid Long value at [Source: org.apache.catalina.connector.CoyoteInputStream@9ba3afe; line: 1, column: 69] (through reference chain: com.ggg.agr.core.model.UserAgreement["operatorId"]) at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163) at org.codehaus.jackson.map.deser.StdDeserializationContext.weirdStringException(StdDeserializationContext.java:243) at org.codehaus.jackson.map.deser.std.StdDeserializer._parseLong(StdDeserializer.java:351) at org.codehaus.jackson.map.deser.std.StdDeserializer$LongDeserializer.deserialize(StdDeserializer.java:846) at org.codehaus.jackson.map.deser.std.StdDeserializer$LongDeserializer.deserialize(StdDeserializer.java:833) at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299) at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414) at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697) at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580) at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2732) at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1923) at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readJavaType(MappingJacksonHttpMessageConverter.java:166) ... 38 more ```

Original source