1

I am having troubles figuring out why I am getting this error on server: I built a simple Controller that accepts a Request Dto:

@ResponseBody
@PostMapping("/log")
fun logReuqest(@RequestBody request: Request) {
    logger.info("Request: $request")
}

The Reuest and the nested DTOs look like this:

data class Request(val customer: Customer, val dealer: Dealer)
data class Customer(val id: String, val name: String)
data class Dealer( val id: String, val name: String)

The json I am sending is:

 {
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }

}

The problem: I send a reuquest from postman with "application/json" contentType header, it works perfectly. But whenever I deploy this app and it gets an incoming request from another service, I get this Jackson error:

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot 
construct instance of `com.service.dto.Request` (although at least one Creator exists): no
String-argument constructor/factory method to deserialize from String value

What is wrong with the code, why does it work from postman and whenever I deploy it doesn't work on when getting incoming requests from another service with restTemplate?

StackTrace:

 
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }')
 at [Source: (PushbackInputStream); line: 1, column: 1]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:387)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342)
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:186)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:158)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:764)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Unknown Source)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
  "customer" : {
    "id" : "123",
    "name" : "customer"
  },
  "dealer" : {
    "id" : "123",
    "name" : "dealer"
  }')
 at [Source: (PushbackInputStream); line: 1, column: 1]
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1588)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1213)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:311)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1495)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:207)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3601)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:376)
    ... 56 common frames omitted 

UPDATE:

The sender app that is sending the request has a configured restTemplate with a messageConverter. How can I solve it from the receiver app?

@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    return createObjectMapperBuilder();
}

private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(jackson2ObjectMapperBuilder().build());
    return converter;
}

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    RestTemplate restTemplate = builder.build();
    restTemplate.setMessageConverters(Collections.singletonList(jackson2HttpMessageConverter()));
    return restTemplate;
}
Deksterious
  • 116
  • 2
  • 12
  • I think your issue is that the "production clients" (using `restTemplate`) are forming a different request. Can you show the `restTemplate` code? Side note: you probably want to test the actual code in a non-prod env before pushing to prod :) – chrsblck Mar 07 '21 at 20:43
  • No real production here, bu production I meant after deployment. I doubt it is the restTemplate of the sending service. There is also nothing special about it's configuration, I've checked it twice. I also tried receiving a String or Json and the objectMapper failed to readValue of the json. – Deksterious Mar 08 '21 at 07:45
  • is your project kotlin first language project? or java and kotlin added? – silentsudo Mar 08 '21 at 11:17
  • Can you debug restTemplate of what is being sent? Also can you please try this sample code or is your code similar to this https://gist.github.com/silentsudo/255581d2bc74e533be76d29e0bf834c3 – silentsudo Mar 08 '21 at 11:21
  • Also check if this link helps https://stackoverflow.com/questions/48448079/json-parse-error-can-not-construct-instance-of-io-starter-topic-topic/48448121 – silentsudo Mar 08 '21 at 11:26
  • I have tried it, it works on my local machine, but such things doesn't work on the server.. I also tried to convert the String to Json and whenever I try `requestJson?.has("customer")` it works on local, but on the server I get false! something is wrong with fasterxml-Jackson server-side. I just don't know what is it. – Deksterious Mar 08 '21 at 16:48
  • And thanks for taking the time to write that code! I am an old java developer and I have never had such problem with Jackson. But it seems there is something special about Jackson and Kotlin that I can't figure out. the controller is not receiving any Objects because it can't serialise it and when I try mapping json/string to objects I get nothing and I have weird behaviours. – Deksterious Mar 08 '21 at 16:56
  • @Deksterious The code you posted is fine. And as you said, it works with locally with Postman. Can you replicate with the `restTemplate`? You should post that code here as well. – chrsblck Mar 08 '21 at 19:25
  • Ok, I guess I found the issue, the restTemplate that is sending the postRequests is configured with MessageConverters! This is causing the problem. The MappingJackson2HttpMessageConverter. But I don't know yet why. I created another app with a simple restTemplate and it works. with the messageConverter it doesn't work – Deksterious Mar 08 '21 at 20:06
  • I updated the Question – Deksterious Mar 08 '21 at 20:11
  • @Deksterious Can you share how this is actually used? E.g., the code around the `restTemplate.postForEntity(...)` – chrsblck Mar 08 '21 at 22:07

1 Answers1

1

Your question is missing the actual restTemplate calls, so without that I'm guessing a bit...

I presume your example restTemplate is sending the JSON request body as a string.

That's what this causedBy is telling you:

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.service.dto.Request (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{

Take a look at these two example REST endpoints and try them locally yourself

@PostMapping("/log")
fun logRequest(@RequestBody request: Request): Request {
    println("/log request: $request")
    return request
}

@PostMapping("/log/string")
fun logRequestString(@RequestBody request: String): String {
    println("/log/string request: $request")
    return request
}

Example RestTemplate calls

val restTemplate = RestTemplate().apply {
    messageConverters = listOf(jackson2HttpMessageConverter())
}

// Sending payload as a JSON Object
val requestBody = Request(Customer("id", "name"), Dealer("id", "name"))
val response = restTemplate.postForEntity(url, HttpEntity(requestBody), Object::class.java)
// Server logs: 
// -> /log request: Request(customer=Customer(id=id, name=name), dealer=Dealer(id=id, name=name))

// Sending payload as a string
val headers = HttpHeaders().apply {
    contentType = MediaType.APPLICATION_JSON
}
val response2 = restTemplate.postForEntity(
    "$url/string",
    HttpEntity(jacksonObjectMapper().writeValueAsString(requestBody), headers),
    Object::class.java
)
// Server logs:
// -> /log/string request: "{\"customer\":{\"id\":\"id\",\"name\":\"name\"},\"dealer\":{\"id\":\"id\",\"name\":\"name\"}}"

If you remove this endpoint, you'll see this same error no String-argument constructor

@PostMapping("/log/string")
fun logRequestString(@RequestBody request: String): String {
    println("/log/string request: $request")
    return request
}
chrsblck
  • 3,948
  • 2
  • 17
  • 20