Spring Boot2系からStringをRestControllerで返している場合にデフォルトのContent-Typeが変わってしまった話

変わってしまった。 これは特殊な条件下によって起きる。

どのようなコードだったか

@RestController
public class MyController {

   @GetMapping
   public ResponseEntity<String> get() {
      return ResponseEntity.ok("Hello World");
   }
}

どうなったか

手元のSpring Boot1系ではRestControllerでStringを返した場合にJSONのContent-Typeで "Hello World" といった形で quote付きでbodyに書き込まれていた。 Spring Boot2系にしたところ、Hello World といった形でquoteなしになってしまった

どういうケースで発生するか

条件としては以下.

  • Spring BootにあるWebMvcのAutoConfigurationを使っている
    • 大体は自分でEnableWebMvcを使っていないケース
  • 以下のような形でWebMvcConfigurerの実装でMessageConverterを追加している
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }
    
}

影響と対処法

JSONとして処理している箇所の修正が発生する。

対処法1: Acceptヘッダをつける

クライアント側のコードを修正して、Acceptヘッダでapplication/jsonなどを指定すればちゃんと思った通りの動作になる。

対処法2: クソイディオムを使う

これはHttpMessageConvertersでも使われている デフォルトのMessageConverterを取得する処理を追加して 自分たちのMessageConverterが優先されるようにしています。 いつの日か動かなくなる可能性があります。

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

-  @Override
-  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
-      converters.add(new MappingJackson2HttpMessageConverter());
-  }

+   @Bean
+   public HttpMessageConverters httpMessageConverters() {
+       return new HttpMessageConverters(/* add default converter */ false, Collections.singletonList(new MappingJackson2HttpMessageConverter()));
+   }
    
+   @Override
+   public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+       converters.addAll(new WebMvcConfigurationSupport() {
+           public List<HttpMessageConverter<?>> defaultMessageConverters() {
+               return super.getMessageConverters();
+           }
+       }.defaultMessageConverters());
+   }
    
}

対処法3: そもそもStringを返さない

多分これが一番ラクな気がする。 エンドポイント数が多いと死亡。

jacksonのTextNodeで返すと良さそう。

対処法4: Spring BootのWebMvcを使わない

あんまりやりたくない

まとめ

今回の例は非常にエッジケースの問題ですが そもそもStringをJSONとして返さないでも良くない?って話はあると思います。

詳しく調べていないが、初期化順序が変わった模様?