Spring Boot 2.2 から Content-Type: application/json に charset=UTF-8 が付かない

## 概要

- Spring Boot 2.2 から @RestController にて JSON を返す際の HTTP レスポンスヘッダで Content-Type: application/json になる (charset=UTF-8が付かない)
- Spring Boot 2.1 では @RestController にて JSON を返す際の HTTP レスポンスヘッダで Content-Type: application/json;charset=UTF-8 になる

## 検証用ソースコード

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

```java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.nio.charset.StandardCharsets;
import java.util.Map;

@SpringBootApplication
@RestController
public class MyApp {

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

  // Spring Boot 2.2 から Content-Type に charset=UTF-8 が付かない
  @GetMapping("/foo")
  public Map foo() {
    return Map.of("message", "こんにちわ、世界。");
  }

  // HTTP レスポンスヘッダを自前で指定することは可能
  @GetMapping("/bar")
  public ResponseEntity bar() {
    Map body = Map.of("message", "こんにちわ、世界。");
    // Content-Type: application/json;charset=UTF-8 を指定
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8));
    return new ResponseEntity<Map>(body, headers, HttpStatus.OK);
  }
}
```

### Spring Boot 2.1 用 Gradle ビルドスクリプト

ファイル名 2.1-build.gradle で保存しておく。

```groovy
plugins {
  id 'org.springframework.boot' version '2.1.15.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'
}
```

### Spring Boot 2.2 用 Gradle ビルドスクリプト

ファイル名 2.2-build.gradle で保存しておく。

```groovy
plugins {
  id 'org.springframework.boot' version '2.2.8.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'
}
```

## 検証

- 実行環境: macOS Catalina + Java 11 (AdoptOpenJDK 11.0.7)
 
### Spring Boot 2.1

Spring Boot 2.1 でビルドしてサーバを起動。

```
$ gradle -b 2.1-build.gradle bootRun
```

他のコンソールから curl でアクセス。
charset=UTF-8 が付いた Content-Type: application/json;charset=UTF-8 が HTTP レスポンスヘッダに入っている。

```
$ curl --include http://localhost:8080/foo
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 Jun 2020 12:15:23 GMT

{"message":"こんにちわ、世界。"}
```

```
$ curl --include http://localhost:8080/bar
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 Jun 2020 12:15:27 GMT

{"message":"こんにちわ、世界。"}
```

### Spring Boot 2.2

Spring Boot 2.2 でビルドしてサーバを起動。

```
$ gradle -b 2.2-build.gradle bootRun
```

他のコンソールから curl でアクセス。
charset=UTF-8 が付かない Content-Type: application/json が HTTP レスポンスヘッダに入っている。

```
$ curl --include http://localhost:8080/foo
HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 16 Jun 2020 12:16:24 GMT

{"message":"こんにちわ、世界。"}
```

自前で HTTP レスポンスヘッダに charset=UTF-8 を付けた場合は残っている。

```
$ curl --include http://localhost:8080/bar
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 Jun 2020 12:16:27 GMT

{"message":"こんにちわ、世界。"}
```

## JSON の仕様

JSON は UTF-8 でエンコーディングされなければならない。

[事実上最後のJSON仕様「RFC 8259」と「ECMA\-404 2nd Editon」公開。UTF\-8エンコード必須に － Publickey](https://www.publickey1.jp/blog/17/jsonrfc_8259ecma-404_2nd_editonutf-8.html)

> RFC 8259でアップデートされたJSON仕様では、文字のエンコードとして「UTF-8」が必須となりました。

Content-Type に指定される MIME タイプ は application/json で charset は定義されていない。

[RFC 8259: The JavaScript Object Notation \(JSON\) Data Interchange Format](https://www.rfc-editor.org/rfc/rfc8259.html)

> The media type for JSON text is application/json.

> Note:  No "charset" parameter is defined for this registration. Adding one really has no effect on compliant recipients.

## MediaType.APPLICATION_JSON_UTF8 は非推奨

Spring Boot 2.1 が依存している Spring Framework 5.1 の MediaType.APPLICATION_JSON_UTF8 では許容されていた。

[MediaType \(Spring Framework 5\.1\.16\.RELEASE API\) \- Javadoc 日本語訳](https://spring.pleiades.io/spring-framework/docs/5.1.16.RELEASE/javadoc-api/org/springframework/http/MediaType.html)

> public static final MediaType APPLICATION_JSON_UTF8
> application/json;charset=UTF-8 のパブリック定数メディアタイプ。
> この APPLICATION_JSON バリアントは、JSON コンテンツタイプの設定に使用する必要があります。RFC7159 では「この登録には文字セットパラメーターが定義されていません」と明記されていますが、ブラウザによっては UTF-8 特殊文字を正しく解釈する必要があるためです

Spring Boot 2.2 が依存している Spring Framework 5.2 から MediaType.APPLICATION_JSON_UTF8 は非推奨(@Deprecated)となっている。

[MediaType \(Spring Framework 5\.2\.7\.RELEASE API\) \- Javadoc 日本語訳](https://spring.pleiades.io/spring-framework/docs/5.2.7.RELEASE/javadoc-api/org/springframework/http/MediaType.html)

> APPLICATION_JSON_UTF8
> 使用すべきではありません。
> Chrome のような主要なブラウザは仕様に準拠し、charset=UTF-8 パラメーターを必要とせずに UTF-8 の特殊文字を正しく解釈するため、5.2 の時点で APPLICATION_JSON を推奨しています。

[Deprecate MediaType\.APPLICATION\_JSON\_UTF8 in favor of APPLICATION\_JSON · Issue \#22788 · spring\-projects/spring\-framework · GitHub](https://github.com/spring-projects/spring-framework/issues/22788)

> 1. RFC 7159 has been obsoleted by RFC 8259
> 2. That states "No "charset" parameter is defined for this registration. Adding one really has no effect on compliant recipients." (at the end of Section 11)
> 3. ...and browsers have indeed fixed their code.
> 
> So it would be best to clarify the text and deprecate the constant.

## 参考資料
- [Deprecate MediaType\.APPLICATION\_JSON\_UTF8 in favor of APPLICATION\_JSON · Issue \#22788 · spring\-projects/spring\-framework · GitHub](https://github.com/spring-projects/spring-framework/issues/22788)
- [Spring MVCのコントローラでの戻り値いろいろ \- Qiita](https://qiita.com/tag1216/items/3680b92cf96eb5a170f0)
- [Spring 5でレスポンスにヘッダを設定する方法](https://www.codeflow.site/ja/article/spring-response-header)
- [How to make spring boot default to application/json;charset=utf\-8 instead of application/json;charset=iso\-8859\-1 \- Stack Overflow](https://stackoverflow.com/questions/55640629/how-to-make-spring-boot-default-to-application-jsoncharset-utf-8-instead-of-app)
- [438464 \- json displayed with wrong encoding \(i\.e\. not unicode\) \- chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=438464)
- [Media Types (Internet Assigned Numbers Authority)](https://www.iana.org/assignments/media-types/media-types.xhtml)