RestTemplate でレスポンスの JSON とクラスのマッピングに失敗した場合に発生する例外

## 概要

- RestTemplate で HTTP レスポンスの JSON とマッピングするクラスが合わない場合に発生する例外を調査する

## 環境

- Spring Boot 2.3.1 (Spring Framework 5.2.7 + Jackson Databind 2.11.0)
- Java 11 (AdoptOpenJDK 11.0.7+10)
- Gradle 6.5
- macOS 10.15.5 Catalina

## 検証用コード

### ファイル一覧

```
├── build.gradle
└── src
    └── main
        └── java
            └── com
                └── example
                    ├── DemoApplication.java
                    └── MyResponse.java
```

### build.gradle

```groovy
plugins {
  id 'org.springframework.boot' version '2.3.1.RELEASE'
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
  id 'java'
}

group = 'com.example'
version = '0.0.1'
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
}
```

### src/main/java/com/example/MyResponse.java

JSON をマッピングするクラス。

```java
package com.example;

import java.util.List;

public class MyResponse {

  public String name;
  public List<MyData> list; // ここに想定外の値をマッピング試行した際の例外を調べる

  public static class MyData {
    public String foo;
    public String bar;
  }
}
```

### src/main/java/com/example/DemoApplication.java

```java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.List;
import java.util.Map;

@SpringBootApplication
@RestController
public class DemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

  // OK: JSON を返却
  @GetMapping("/ok")
  public Map ok() {
    return
      Map.of(
        "name", "myname",
        "list", List.of(
          Map.of("foo", "myfoo", "bar", "mybar")
        )
      );
  }

  // NG1: MyResponse に合わない JSON を返却
  @GetMapping("/ng1")
  public Map ng1() {
    return
      Map.of(
        "name", "myname",
        "list", "mylist" // List があるべき場所に文字列 "mylist"
      );
  }

  // NG2: MyResponse に合わない JSON を返却
  @GetMapping("/ng2")
  public Map ng2() {
    return
      Map.of(
        "name", "myname",
        "list", "" // List があるべき場所に空文字列
      );
  }

  // NG3: MyResponse に合わない JSON を返却
  @GetMapping("/ng3")
  public Map ng3() {
    return
      Map.of(
        "name", "myname",
        "list", List.of("mylist") // MyData があるべき場所に文字列 "mylist"
      );
  }

  // NG4: MyResponse に合わない JSON を返却
  @GetMapping("/ng4")
  public Map ng4() {
    return
      Map.of(
        "name", "myname",
        "list", List.of("") // MyData があるべき場所に空文字列
      );
  }

  @GetMapping("/mydata/{path}")
  public MyResponse mydata(@PathVariable("path") String path) throws Exception {
    try {
      // 自らのサーバから JSON を取得
      String uri = "http://localhost:8080/" + path;
      RequestEntity re = RequestEntity.get(new URI(uri)).build();
      RestTemplate restTemplate = new RestTemplate();

      // JSON を MyResponse に変換
      // JSON が MyResponse に合わなければここで例外が発生する
      ResponseEntity<MyResponse> res = restTemplate.exchange(re, MyResponse.class);

      return res.getBody();

    } catch (Exception e) {

      // どういう例外が発生しているか出力する
      System.out.println("例外クラス: " + e.getClass().getName());

      // 原因となった例外のチェーンを出力する
      Throwable cause = e;
      while ((cause = cause.getCause()) != null) {
        System.out.println("原因例外クラス: " + cause.getClass().getName());
      }

      throw e;
    }
  }
}
```

## Spring Boot アプリケーションを起動

```
$ gradle bootRun
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)
```

## 検証

他のコンソールから curl で HTTP リクエストを投げて、どのような例外が発生するかを検証する。

### OK: MyResponse にマッピングできる JSON

```
$ curl http://localhost:8080/ok
{"list":[{"foo":"myfoo","bar":"mybar"}],"name":"myname"}
```

```
$ curl http://localhost:8080/mydata/ok 
{"name":"myname","list":[{"foo":"myfoo","bar":"mybar"}]}
```

### NG1: List があるべき場所に文字列 "mylist"

クラスにマッピング試行する JSON データ。

```
$ curl http://localhost:8080/ng1
{"list":"mylist","name":"myname"}
```

JSON データをクラスにマッピングする処理をする URL にアクセス。

```
$ curl http://localhost:8080/mydata/ng1
{"timestamp":"2020-06-22T21:05:59.317+00:00","status":500,"error":"Internal Server Error","message":"","path":"/mydata/ng1"}
```

Spring Boot サーバ側のログ (読みやすくするため一部を抜粋して整形したもの)。

```
例外クラス: org.springframework.web.client.RestClientException
原因例外クラス: org.springframework.http.converter.HttpMessageNotReadableException
原因例外クラス: com.fasterxml.jackson.databind.exc.MismatchedInputException

2020-06-23 06:05:59.311 ERROR 4196 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    :
 Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;

nested exception is org.springframework.web.client.RestClientException:
 Error while extracting response for type [class com.example.MyResponse] and content type [application/json];

nested exception is org.springframework.http.converter.HttpMessageNotReadableException:
 JSON parse error: Cannot deserialize instance of `java.util.ArrayList<com.example.MyResponse$MyData>` out of VALUE_STRING token;

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException:
 Cannot deserialize instance of `java.util.ArrayList<com.example.MyResponse$MyData>` out of VALUE_STRING token
 at [Source: (PushbackInputStream); line: 1, column: 25] (through reference chain: com.example.MyResponse["list"])] with root cause
```

Spring Boot サーバ側のログ (出力そのまま)。

```
例外クラス: org.springframework.web.client.RestClientException
原因例外クラス: org.springframework.http.converter.HttpMessageNotReadableException
原因例外クラス: com.fasterxml.jackson.databind.exc.MismatchedInputException
2020-06-23 06:05:59.311 ERROR 4196 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.RestClientException: Error while extracting response for type [class com.example.MyResponse] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.ArrayList<com.example.MyResponse$MyData>` out of VALUE_STRING token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<com.example.MyResponse$MyData>` out of VALUE_STRING token
 at [Source: (PushbackInputStream); line: 1, column: 25] (through reference chain: com.example.MyResponse["list"])] with root cause

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<com.example.MyResponse$MyData>` out of VALUE_STRING token
 at [Source: (PushbackInputStream); line: 1, column: 25] (through reference chain: com.example.MyResponse["list"])
        at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1464) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1238) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1190) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:331) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:264) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:371) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482) ~[jackson-databind-2.11.0.jar:2.11.0]
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3487) ~[jackson-databind-2.11.0.jar:2.11.0]
        at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:269) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:257) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:105) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:998) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:981) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:741) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:641) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at com.example.DemoApplication.mydata(DemoApplication.java:86) ~[main/:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.36.jar:9.0.36]
        at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
```

### NG2: List があるべき場所に空文字列

クラスにマッピング試行する JSON データ。

```
$ curl http://localhost:8080/ng2
{"list":"","name":"myname"}
```

JSON データをクラスにマッピングする処理をする URL にアクセス。

```
$ curl http://localhost:8080/mydata/ng2
{"timestamp":"2020-06-22T21:07:21.986+00:00","status":500,"error":"Internal Server Error","message":"","path":"/mydata/ng2"}
```

Spring Boot サーバ側のログ (読みやすくするため一部を抜粋して整形したもの)。

```
例外クラス: org.springframework.web.client.RestClientException
原因例外クラス: org.springframework.http.converter.HttpMessageNotReadableException
原因例外クラス: com.fasterxml.jackson.databind.exc.MismatchedInputException

2020-06-23 06:07:21.984 ERROR 4196 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    :
 Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;

nested exception is org.springframework.web.client.RestClientException:
 Error while extracting response for type [class com.example.MyResponse] and content type [application/json];

nested exception is org.springframework.http.converter.HttpMessageNotReadableException:
 JSON parse error: Cannot construct instance of `java.util.ArrayList` (although at least one Creator exists):
 no String-argument constructor/factory method to deserialize from String value ('');

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException:
 Cannot construct instance of `java.util.ArrayList` (although at least one Creator exists):
 no String-argument constructor/factory method to deserialize from String value ('')
 at [Source: (PushbackInputStream); line: 1, column: 25] (through reference chain: com.example.MyResponse["list"])] with root cause
```

### NG3: MyData があるべき場所に文字列 "mylist"

クラスにマッピング試行する JSON データ。

```
$ curl http://localhost:8080/ng3
{"list":["mylist"],"name":"myname"}
```

JSON データをクラスにマッピングする処理をする URL にアクセス。

```
$ curl http://localhost:8080/mydata/ng3
{"timestamp":"2020-06-22T21:08:08.316+00:00","status":500,"error":"Internal Server Error","message":"","path":"/mydata/ng3"}
```

Spring Boot サーバ側のログ (読みやすくするため一部を抜粋して整形したもの)。

```
例外クラス: org.springframework.web.client.RestClientException
原因例外クラス: org.springframework.http.converter.HttpMessageNotReadableException
原因例外クラス: com.fasterxml.jackson.databind.exc.MismatchedInputException

2020-06-23 06:08:08.315 ERROR 4196 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    :
 Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;

nested exception is org.springframework.web.client.RestClientException:
 Error while extracting response for type [class com.example.MyResponse] and content type [application/json];

nested exception is org.springframework.http.converter.HttpMessageNotReadableException:
 JSON parse error: Cannot construct instance of `com.example.MyResponse$MyData` (although at least one Creator exists):
 no String-argument constructor/factory method to deserialize from String value ('mylist');

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException:
 Cannot construct instance of `com.example.MyResponse$MyData` (although at least one Creator exists):
 no String-argument constructor/factory method to deserialize from String value ('mylist')
 at [Source: (PushbackInputStream); line: 1, column: 26] (through reference chain: com.example.MyResponse["list"]->java.util.ArrayList[0])] with root cause
```

### NG4: // MyData があるべき場所に空文字列

クラスにマッピング試行する JSON データ。

```
$ curl http://localhost:8080/ng4
{"list":[""],"name":"myname"}
```

JSON データをクラスにマッピングする処理をする URL にアクセス。

```
$ curl http://localhost:8080/mydata/ng4
{"timestamp":"2020-06-22T21:08:36.354+00:00","status":500,"error":"Internal Server Error","message":"","path":"/mydata/ng4"}
```

Spring Boot サーバ側のログ (読みやすくするため一部を抜粋して整形したもの)。

```
例外クラス: org.springframework.web.client.RestClientException
原因例外クラス: org.springframework.http.converter.HttpMessageNotReadableException
原因例外クラス: com.fasterxml.jackson.databind.exc.MismatchedInputException
2020-06-23 06:08:36.352 ERROR 4196 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    :
 Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;

nested exception is org.springframework.web.client.RestClientException:
 Error while extracting response for type [class com.example.MyResponse] and content type [application/json];

nested exception is org.springframework.http.converter.HttpMessageNotReadableException:
 JSON parse error: Cannot construct instance of `com.example.MyResponse$MyData` (although at least one Creator exists):
 no String-argument constructor/factory method to deserialize from String value ('');

nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException:
 Cannot construct instance of `com.example.MyResponse$MyData` (although at least one Creator exists):
 no String-argument constructor/factory method to deserialize from String value ('')
 at [Source: (PushbackInputStream); line: 1, column: 26] (through reference chain: com.example.MyResponse["list"]->java.util.ArrayList[0])] with root cause
```

## 発生している例外の公式資料

### org.springframework.web.client.RestClientException

RestClientException はサーバのエラーレスポンス、入出力エラー、レスポンスをデコードする際のエラーなどで発生する例外。

[RestClientException \(Spring Framework 5\.2\.7\.RELEASE API\)](https://docs.spring.io/spring-framework/docs/5.2.7.RELEASE/javadoc-api/org/springframework/web/client/RestClientException.html)

> Base class for exceptions thrown by RestTemplate in case a request fails because of a server error response, as determined via ResponseErrorHandler.hasError(ClientHttpResponse), failure to decode the response, or a low level I/O error.

### org.springframework.http.converter.HttpMessageNotReadableException

親クラスの HttpMessageConversionException の説明によると変換に失敗した際に投げられる例外とのこと。

[HttpMessageNotReadableException \(Spring Framework 5\.2\.7\.RELEASE API\)](https://docs.spring.io/spring/docs/5.2.7.RELEASE/javadoc-api/org/springframework/http/converter/HttpMessageNotReadableException.html)

> Thrown by HttpMessageConverter implementations when the HttpMessageConverter.read(java.lang.Class<? extends T>, org.springframework.http.HttpInputMessage) method fails.

[HttpMessageConversionException \(Spring Framework 5\.2\.7\.RELEASE API\)](https://docs.spring.io/spring/docs/5.2.7.RELEASE/javadoc-api/org/springframework/http/converter/HttpMessageConversionException.html)

> Thrown by HttpMessageConverter implementations when a conversion attempt fails.

### com.fasterxml.jackson.databind.exc.MismatchedInputException

マッピングするクラス定義に合わない JSON が来たときに発生する例外。

[MismatchedInputException \(jackson\-databind 2\.11\.0 API\)](https://fasterxml.github.io/jackson-databind/javadoc/2.11/com/fasterxml/jackson/databind/exc/MismatchedInputException.html)

## 参考資料

- [RestTemplate \(Spring Framework 5\.2\.7\.RELEASE API\)](https://docs.spring.io/spring-framework/docs/5.2.7.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html)