Spring BootやSpring Frameworkを利用してREST APIを開発している際、クライアントからのリクエストに対して「415 Unsupported Media Type」というエラーが返されることがあります。

この時、アプリケーションのログに記録されているのが org.springframework.web.HttpMediaTypeNotSupportedException です。

この例外は、サーバーが受け取ったリクエストのメディアタイプ(Content-Type)を処理できない場合にスローされます。

一見単純なエラーに見えますが、原因はクライアント側の設定ミスからサーバー側の定義不足、あるいはライブラリの依存関係まで多岐にわたります。

本記事では、この例外が発生するメカニズムを解き明かし、実務で役立つ具体的な解決策を詳しく解説します。

HttpMediaTypeNotSupportedExceptionとは何か

org.springframework.web.HttpMediaTypeNotSupportedException は、Spring MVCにおいてクライアントが送信したデータの形式(Content-Type)をサーバー側がサポートしていないことを示す例外です。

HTTPプロトコルでは、リクエストボディにデータを含める際、そのデータがどのような形式(JSON、XML、フォーム形式など)であるかを Content-Type ヘッダーで明示する必要があります。

Springは、このヘッダーを確認し、適切な HttpMessageConverter を選択してJavaオブジェクトへの変換を試みます。

しかし、合致するコンバーターが見つからない場合、この例外が発生し、クライアントにはHTTPステータスコード 415 が返されます。

Content-Typeとメッセージコンバーターの関係

Spring MVCの内部では、リクエストを処理する際に HandlerMappingHttpMessageConverter が重要な役割を果たします。

  1. クライアントが Content-Type: application/json でリクエストを送信する。
  2. Springがコントローラーのメソッドに付与された @RequestBody を検知する。
  3. 登録されているコンバーター(例:Jackson用の MappingJackson2HttpMessageConverter)の中から、指定されたメディアタイプを処理できるものを探す。
  4. 適切なコンバーターがない場合に例外をスローする。

このように、この例外は「データの受け渡しに関する契約不履行」が起きた際に発生するものと言えます。

例外が発生する主な原因

この例外が発生する理由は、大きく分けて「クライアント側の不備」と「サーバー側の実装・設定ミス」の2つに分類できます。

1. クライアント側のContent-Typeヘッダー欠如・誤り

最も頻繁に見られる原因は、クライアントがリクエストを送信する際に Content-Type ヘッダーを正しく設定していない ケースです。

例えば、サーバー側がJSONを期待している(application/json)にもかかわらず、クライアントがヘッダーを省略したり、誤って text/plainapplication/x-www-form-urlencoded を指定したりすると、Springはデータを正しく解釈できず、415エラーを返します。

2. コントローラーのconsumes属性による制約

Springのコントローラーでは、@PostMapping@RequestMappingconsumes 属性を使用して、受け入れるメディアタイプを制限できます。

Java
@PostMapping(value = "/api/data", consumes = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<String> processXml(@RequestBody MyData data) {
    // ...
}

上記のようなコードがある場合、このエンドポイントは application/xml しか受け付けません。

ここにJSON形式のリクエストを送ると、当然ながら例外が発生します。

3. HttpMessageConverterの不足(依存関係の問題)

Spring Bootでは、クラスパスに必要なライブラリが存在する場合、自動的に対応するメッセージコンバーターを登録します。

しかし、例えばJSONを処理したいのに、プロジェクトの依存関係(pom.xmlbuild.gradle)に Jackson Databind が含まれていない場合、SpringはJSONをパースする手段を失い、例外をスローします。

4. リクエストボディの不在

稀なケースとして、コントローラーで @RequestBody を定義しているにもかかわらず、クライアントがボディを空で送信した場合にも発生することがあります。

Springはボディの内容から型を推論しようとしますが、データがないためにメディアタイプの判定に失敗することがあります。

解決策1:クライアント側のリクエスト修正

まずは、クライアント側のリクエスト内容がサーバーの期待通りであるかを確認します。

開発ツール(Postman、cURL、ブラウザのデベロッパーツールなど)を使用して、送信されるヘッダーをチェックしてください。

cURLによる正しいリクエスト例

JSONデータを送信する場合、必ず -H "Content-Type: application/json" を付与する必要があります。

Shell
# 誤ったリクエスト(Content-Typeがない)
curl -X POST http://localhost:8080/api/users -d '{"name":"Taro"}'

# 正しいリクエスト
curl -X POST http://localhost:8080/api/users \
     -H "Content-Type: application/json" \
     -d '{"name":"Taro"}'

フロントエンド(JavaScript/TypeScript)の fetch APIを使用する場合も、明示的にヘッダーを指定することを忘れないようにしましょう。

JavaScript
fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Taro' })
});

解決策2:サーバー側のコントローラー設定の見直し

サーバー側の実装において、受け入れ可能なメディアタイプが適切に設定されているかを確認します。

consumes属性の確認と拡張

もし特定のメディアタイプのみに制限する必要がないのであれば、consumes 属性を削除するか、複数のタイプを許可するように変更 します。

Java
// JSONとXMLの両方を許可する例
@PostMapping(
    value = "/api/users",
    consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }
)
public ResponseEntity<User> createUser(@RequestBody User user) {
    return ResponseEntity.ok(user);
}

@RequestBodyの有無を確認

データをリクエストボディではなく、クエリパラメータやフォームデータとして受け取るべき場合は、@RequestBody ではなく @RequestParam@ModelAttribute を使用するように修正します。

解決策3:依存関係とメッセージコンバーターの確認

プロジェクトに適切なライブラリが含まれているかを確認します。

Spring Boot Starter Web(spring-boot-starter-web)を使用している場合、通常はJacksonが含まれていますが、カスタマイズされたプロジェクトでは注意が必要です。

Mavenの依存関係確認

JSON処理を行うには、以下の依存関係が必要です。

XML
<!-- pom.xml -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

また、XMLを処理したい場合は、JacksonのXML拡張モジュールを追加する必要があります。

これが不足していると、XML送信時に415エラーが発生します。

XML
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

カスタムメッセージコンバーターの登録

特殊なメディアタイプ(独自のバイナリ形式など)を扱う場合は、WebMvcConfigurer を実装して独自の HttpMessageConverter を登録する必要があります。

Java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 独自のコンバーターを追加
        converters.add(new MyCustomMessageConverter());
    }
}

例外の再現とデバッグ方法

実際に例外を発生させ、どのようにログに出力されるかを確認するコード例を以下に示します。

再現用のコントローラー

Java
package com.example.demo.controller;

import com.example.demo.model.User;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/test")
public class TestController {

    // JSONのみを受け付けるエンドポイント
    @PostMapping(value = "/json-only", consumes = MediaType.APPLICATION_JSON_VALUE)
    public String handleJson(@RequestBody User user) {
        return "Received: " + user.getName();
    }
}

実行結果(415エラーの発生)

このエンドポイントに対し、Content-Type: text/plain でリクエストを送ると、以下の結果が得られます。

HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json
{
    "timestamp": "202X-XX-XXTXX:XX:XX.XXX+00:00",
    "status": 415,
    "error": "Unsupported Media Type",
    "path": "/api/test/json-only"
}

サーバーログには以下のようなスタックトレースの一部が出力されます。

Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'text/plain;charset=UTF-8' is not supported]

ログを確認することで、「どのメディアタイプが送られ、何がサポートされていないのか」が一目でわかります。

解決策4:グローバル例外ハンドラーによるカスタマイズ

デフォルトの415エラーレスポンスはシンプルすぎることがあります。

ユーザーやフロントエンド開発者に対して、より親切なメッセージを返すために、@ControllerAdvice を使用して例外をキャッチし、カスタマイズしたレスポンスを返すことができます。

例外ハンドラーの実装例

Java
package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("message", "サポートされていないメディアタイプです");
        
        // サポートされているタイプをリストアップしてレスポンスに含める
        String supportedTypes = ex.getSupportedMediaTypes().stream()
                .map(String::valueOf)
                .collect(Collectors.joining(", "));
        
        body.put("supported_types", supportedTypes);
        body.put("provided_type", String.valueOf(ex.getContentType()));

        return new ResponseEntity<>(body, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
    }
}

このように実装することで、クライアント側は「何を送るべきだったのか」をレスポンスから判断できるようになり、開発効率が向上します。

ベストプラクティス:API設計時の注意点

HttpMediaTypeNotSupportedException を未然に防ぎ、保守性の高いAPIを構築するためのポイントをいくつか紹介します。

1. APIドキュメント(Swagger/OpenAPI)の活用

APIがどのメディアタイプをサポートしているかを、ドキュメントで明示することが重要です。

SpringDoc OpenAPIなどを使用すると、コードから自動的にドキュメントを生成でき、クライアント側の誤解を減らせます。

2. 適切なContent-Typeのデフォルト化

もしAPIのほとんどがJSONを扱うのであれば、共通の設定でJSONを優先するように調整します。

ただし、Spring Bootのデフォルト設定は既にJSONに最適化されているため、無理に設定を弄りすぎないことも重要です。

3. バリデーションの分離

メディアタイプのチェックはSpringのインフラ層で行われますが、その後の「データの形式(中身)が正しいか」のチェックは @Valid などのバリデーション機能で行います。

415エラー(形式が違う)と400エラー(値が不正)を明確に区別して設計することで、エラーハンドリングが整理されます。

まとめ

org.springframework.web.HttpMediaTypeNotSupportedException は、Spring MVCにおけるコンテンツ交渉(Content Negotiation)が失敗した際に発生する標準的な例外です。

その解決策は、多くの場合、以下の3点に集約されます。

クライアント側

リクエストヘッダーに適切な Content-Type(例:application/json)を設定しているか確認する。

サーバー側

コントローラーの consumes 属性やアノテーション設定が、受け取りたいデータ形式と一致しているか確認する。

環境設定

必要なメッセージコンバーター(Jacksonなど)のライブラリがクラスパスに含まれているか確認する。

また、グローバル例外ハンドラーを導入してエラーメッセージを詳細化することで、APIを利用する開発者にとって優しいシステムを構築できます。

415エラーは「通信のルールが守られていない」という重要なサインです。

本記事で解説した手順に沿って、原因の切り分けと対策を行い、堅牢なAPI開発を進めてください。