Java開発において、JSONデータのパース(デシリアライズ)は日常的な作業ですが、その中で最も頻繁に遭遇するエラーの一つが com.fasterxml.jackson.databind.exc.MismatchedInputException です。

この例外は、Jacksonライブラリが提供されたJSONデータをJavaオブジェクトに変換しようとした際、「JSONの構造やデータ型が、受け取り側のJavaクラスの定義と一致しない」場合にスローされます。

Web APIのレスポンス形式が変更された場合や、フロントエンドから送られてくるリクエストボディの構造が想定と異なる場合など、発生原因は多岐にわたります。

この記事では、テクニカルライターの視点から、この例外が発生する具体的な原因とその解決策、さらには未然に防ぐためのベストプラクティスまで、コード例を交えて詳しく解説します。

MismatchedInputExceptionとは何か

MismatchedInputException は、JavaのJSON処理ライブラリであるJacksonの databind モジュールに含まれる例外クラスです。

この例外は JsonMappingException のサブクラスであり、主にデシリアライズ(JSONからJavaオブジェクトへの変換)プロセス中に発生します。

この例外の最大の特徴は、「入力されたJSONトークンの種類が、期待されている型に対して不適切である」ことを示している点にあります。

例えば、Java側では単一のオブジェクトを期待しているのに、JSON側では配列([])が渡された場合などがこれに該当します。

エラーメッセージには、どの型に変換しようとして失敗したのか、どのトークン(START_ARRAYやSTART_OBJECTなど)が見つかったのかが詳細に記載されるため、メッセージを読み解くことが解決への近道となります。

主な発生原因と解決策

MismatchedInputException が発生する主なシナリオは、大きく分けて4つのパターンに分類できます。

それぞれの原因と具体的な対処法を見ていきましょう。

1. 構造の不一致(オブジェクト vs 配列)

最も一般的な原因は、Javaのデータ構造とJSONの階層構造が一致していないケースです。

原因の解説

Java側でクラス(POJO)を定義しているにもかかわらず、JSON側がそのクラスのインスタンスではなく、そのクラスのリスト(配列)になっている場合に発生します。

逆のパターン(配列を期待しているのに単一のオブジェクトが来る)も同様です。

具体的なコード例

以下の例では、単一の User オブジェクトを期待しているところに、配列形式のJSONを流し込んでいます。

Java
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;

@Data
class User {
    private String name;
    private int age;
}

public class Main {
    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        // JSONは配列形式
        String jsonInput = "[{\"name\": \"Tanaka\", \"age\": 25}]";

        try {
            // 単一のUserクラスとして読み込もうとする
            User user = mapper.readValue(jsonInput, User.class);
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
実行結果
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `User` from Array value (token `JsonToken.START_ARRAY`)
 at [Source: (String)"[{"name": "Tanaka", "age": 25}]"; line: 1, column: 1]

解決策

この場合、受け取り側の型を List<User> または User[] に変更する必要があります。

Java
// 解決策:Listとして受け取る
List<User> users = mapper.readValue(jsonInput, new TypeReference<List<User>>() {});

あるいは、もしJSON側が常に要素1つの配列で送られてくることが確定しており、どうしても単一オブジェクトとして扱いたい場合は、Jacksonの設定で ACCEPT_SINGLE_VALUE_AS_ARRAY を有効にすることを検討しますが、基本的には型を合わせるのが正攻法です。

2. プリミティブ型への null 代入

Javaのプリミティブ型(int, boolean, long など)に対して、JSONの null をマッピングしようとするとこの例外が発生します。

原因の解説

Javaのプリミティブ型は null を保持できません。

JSONデータ内で対象のフィールドが null になっている、あるいはフィールド自体が存在しない場合にデフォルトで null を入れようとして失敗します。

具体的なコード例

Java
@Data
class Product {
    private String name;
    private int price; // プリミティブ型
}

// JSON側でpriceがnull
String jsonInput = "{\"name\": \"Notebook\", \"price\": null}";

解決策

最も推奨される解決策は、プリミティブ型をラッパークラス(Integer, Booleanなど)に変更することです。

Java
@Data
class Product {
    private String name;
    private Integer price; // ラッパークラスに変更
}

もし、どうしてもプリミティブ型のまま扱いたい場合は、Jacksonの DeserializationFeature を設定して、nullをデフォルト値(0やfalse)に変換するように挙動を変更することも可能です。

Java
ObjectMapper mapper = new ObjectMapper();
// nullをプリミティブのデフォルト値に変換する設定
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);

3. 入力データが空(Empty Input)

APIのリクエストボディが空の状態で送信された場合にも、この例外がスローされます。

原因の解説

mapper.readValue(input, Target.class) を呼び出した際、input が空文字 "" や中身のないストリームである場合、Jacksonは「マッピングすべきコンテンツが存在しない」と判断します。

エラーメッセージの例

com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
 at [Source: (String)""; line: 1, column: 0]

解決策

入力データが空である可能性を考慮し、デシリアライズ前にチェックを行うか、空の入力を許容する設定を追加します。

Java
// 入力チェックを行う
if (jsonInput == null || jsonInput.isEmpty()) {
    return null; // またはデフォルトのインスタンスを返す
}

4. 列挙型(Enum)の不一致

Javaの Enum 型に対して、定義されていない文字列をJSONから渡した場合も、この例外の対象となります(設定によっては InvalidFormatException になることもあります)。

具体的なコード例

Java
enum Status {
    OPEN, CLOSED
}

@Data
class Task {
    private Status status;
}

// JSON側で定義外の値を渡す
String jsonInput = "{\"status\": \"PENDING\"}";

解決策
  • JSON側の値を修正する: 定義されている OPEN または CLOSED を送るようにします。
  • @JsonProperty を使用する: JSON上の表記とEnum定数名をマッピングします。
  • 未知の値を無視する設定: 不明なEnum値が来た場合に null として扱うように設定します。
Java
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);

高度な解決策:カスタムデシリアライザの利用

構造が非常に複雑であったり、動的に型が変わるような特殊なJSONを扱う場合、標準のマッピング機能だけでは MismatchedInputException を回避できないことがあります。

その際の最終手段が カスタムデシリアライザ の実装です。

カスタムデシリアライザの実装例

例えば、あるフィールドが「文字列」で来ることもあれば「オブジェクト」で来ることもあるという、一貫性のない外部APIを扱う場合を想定します。

Java
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import java.io.IOException;

@Data
class FlexibleResponse {
    @JsonDeserialize(using = SmartDataDeserializer.class)
    private String data;
}

class SmartDataDeserializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonNode node = p.getCodec().readTree(p);
        if (node.isObject()) {
            // オブジェクトなら文字列表現に変換
            return node.toString();
        }
        // 文字列ならテキスト値を返す
        return node.asText();
    }
}

このように JsonDeserializer を継承して自作することで、入力トークンの種類をコード内で判定し、柔軟にマッピング処理を行うことができます。

これにより、構造の不一致による例外を根本から防ぐことが可能です。

Jacksonのグローバル設定による対策

個別のクラスに対処するだけでなく、プロジェクト全体でJacksonの挙動を調整することで、不意な例外の発生を抑制できます。

推奨されるDeserializationFeatureの設定

以下に、MismatchedInputException を回避するために役立つ主な設定をまとめます。

設定項目説明推奨設定
FAIL_ON_UNKNOWN_PROPERTIESJSONに未知のフィールドがある場合に失敗するかfalse
FAIL_ON_NULL_FOR_PRIMITIVESプリミティブ型にnullを入れようとした際エラーにするかfalse (ケースバイケース)
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT空文字をnullオブジェクトとして扱うかtrue
ACCEPT_SINGLE_VALUE_AS_ARRAY単一値を配列として受け取ることを許容するかtrue (柔軟性重視なら)

設定の反映方法は以下の通りです。

Java
ObjectMapper mapper = new ObjectMapper()
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false)
    .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Spring Bootを使用している場合は、application.yml に記述することで一括設定が可能です。

YAML
spring:
  jackson:
    deserialization:
      fail-on-unknown-properties: false
      fail-on-null-for-primitives: false
      accept-single-value-as-array: true

開発フェーズでのデバッグ手法

もし MismatchedInputException が発生してしまった場合、効率的に原因を特定するためのステップを紹介します。

STEP1
エラーメッセージの「token」を確認する

token: JsonToken.START_ARRAY とあれば「配列が来た」、START_OBJECT とあれば「オブジェクトが来た」ことを意味します。

Java側の期待値と突き合わせましょう。

STEP2
エラー位置(line, column)を確認する

例外メッセージの最後に表示される line: 1, column: 15 といった情報を元に、JSONデータのどの場所で解析が止まったかを特定します。

STEP3
raw JSONをログに出力する

デシリアライズに失敗する直前の生のJSON文字列をログに出力し、想定通りの構造になっているかを目視で確認します。

STEP4
オンラインパーサーを活用する

複雑なJSONの場合、JSONLint などのツールを使用して、そもそもJSONとして妥当(Valid)かどうかを確認することも重要です。

まとめ

com.fasterxml.jackson.databind.exc.MismatchedInputException は、一見すると厄介なエラーですが、その本質は「JavaとJSONの間の契約違反」を教えてくれる親切な通知です。

主な対策を振り返ると以下の通りです。

  • 構造の不一致には、受け取り側の型(List化など)を見直す。
  • プリミティブ型の問題には、ラッパークラス(Integerなど)への変更を検討する。
  • 空の入力には、事前のnull/空チェックを実装する。
  • 柔軟な対応が必要な場合は、Jacksonの DeserializationFeature やカスタムデシリアライザを活用する。

堅牢なシステムを構築するためには、単に例外を握りつぶすのではなく、なぜ不一致が起きているのかを正しく理解し、型安全な設計を心がけることが大切です。

この記事で紹介したテクニックを活用し、スムーズなデータ連携を実現してください。