Javaを用いたWebアプリケーション開発やマイクロサービス間の通信において、JSON形式のデータ交換は標準的な手法となっています。

その中で、JavaオブジェクトとJSONを相互に変換するライブラリとして最も広く利用されているのが「Jackson」です。

しかし、外部APIとの連携やフロントエンドからのリクエスト処理を行っている際に、頻繁に遭遇するのがcom.fasterxml.jackson.databind.exc.UnrecognizedPropertyExceptionという例外です。

この例外は、JSONデータの中にJavaクラス(DTO)で定義されていないプロパティが含まれている場合に発生します。

本記事では、この例外が発生する根本的な原因から、開発現場で即座に使える具体的な解決策、さらには設計上のベストプラクティスまでをプロの視点で詳しく解説します。

UnrecognizedPropertyExceptionが発生する原因

Jacksonのデフォルトの動作は、非常に厳格な「スキーマ整合性」に基づいています。

JSONデータをJavaオブジェクトにデシリアライズ(変換)する際、JacksonはJSON内のすべてのキーに対して、対応するJavaクラスのフィールドまたはセッターが存在することを期待します。

もしJSON側にJavaクラスで定義されていない未知のプロパティが含まれていた場合、Jacksonは「このデータをどこにマッピングすべきか判断できない」として、UnrecognizedPropertyExceptionをスローします。

この挙動が発生する主なシナリオは以下の通りです。

APIの仕様変更

連携先の外部APIがアップデートされ、レスポンスに新しいフィールドが追加されたが、自システムのDTOクラスを更新していない場合。

不要なデータの混入

フロントエンドから送信されるリクエストに、サーバー側では必要のない補助的なデータが含まれている場合。

タイポ(打ち間違い)

JSONのキー名とJavaのフィールド名がスペルミスなどで一致していない場合。

ポリモーフィズムの不一致

継承関係にあるクラス構造で、サブクラス固有のプロパティを親クラスとして受け取ろうとした場合。

デフォルトで例外を投げる仕様は、「データの不整合を早期に検知する」という観点では有用ですが、疎結合なシステム間連携においては、軽微な変更でシステムが停止してしまうリスクを孕んでいます。

例外が発生する具体的なコード例

まずは、どのようなコードでこの例外が発生するのか、具体的なプログラムを見てみましょう。

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

public class JacksonErrorDemo {
    public static void main(String[] args) {
        // Javaクラスに存在しない "age" というプロパティが含まれるJSON
        String jsonInput = "{\"name\": \"Taro\", \"age\": 25}";

        try {
            ObjectMapper mapper = new ObjectMapper();
            User user = mapper.readValue(jsonInput, User.class);
            System.out.println("Name: " + user.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class User {
    private String name;

    // Getter and Setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

実行結果(出力内容)

上記のコードを実行すると、コンソールには以下のようなスタックトレースが出力されます。

実行結果
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "age" (class User), not marked as ignorable (one known property: "name"])
 at [Source: (String)"{"name": "Taro", "age": 25}"; line: 1, column: 22] (through reference chain: User["age"])

このように、age というフィールドが User クラスに見当たらないため、デシリアライズに失敗していることがわかります。

解決策1:@JsonIgnoreProperties アノテーションを使用する

最も一般的かつ推奨される解決策は、Javaクラス(DTO)に対して @JsonIgnoreProperties(ignoreUnknown = true) アノテーションを付与することです。

これにより、そのクラスに定義されていない未知のプロパティがJSONに含まれていても、単に無視して処理を続行するようになります。

実装例

Java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
class User {
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

この方法のメリットは、クラス単位で制御ができる点です。

特定のAPIレスポンス用DTOに対してのみ適用したい場合に最適です。

システム全体の動作を変えることなく、安全に未知のプロパティを許容できます。

解決策2:ObjectMapperの設定をグローバルに変更する

プロジェクト全体で、未知のプロパティを常に無視するように設定したい場合は、ObjectMapper 自体の設定を変更します。

実装例

Java
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class GlobalConfigDemo {
    public static void main(String[] args) throws Exception {
        String jsonInput = "{\"name\": \"Taro\", \"age\": 25}";

        ObjectMapper mapper = new ObjectMapper();
        // 未知のプロパティがあっても失敗させない設定を有効化
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        User user = mapper.readValue(jsonInput, User.class);
        System.out.println("Successfully deserialized: " + user.getName());
    }
}
実行結果
Successfully deserialized: Taro

この方法は、マイクロサービスなどで「外部からのデータは常に拡張される可能性がある」という前提がある場合に非常に有効です。

ただし、プロジェクト内のすべてのデシリアライズ処理に影響を与えるため、本来検知すべきデータの不備(フィールド名のスペルミスなど)も見逃してしまう可能性がある点に注意が必要です。

解決策3:Spring Boot環境での設定

Spring Bootを使用している場合、ObjectMapper は通常、DIコンテナによって管理されています。

そのため、個別にインスタンス化するのではなく、設定ファイル(application.yml など)で一括制御するのが一般的です。

application.yml での設定

YAML
spring:
  jackson:
    deserialization:
      fail-on-unknown-properties: false

または、JavaのConfigurationクラスでカスタマイズすることも可能です。

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        return new Jackson2ObjectMapperBuilder()
                .failOnUnknownProperties(false);
    }
}

Spring BootのWebスターターを使用している場合、この設定を行うだけで、コントローラーの引数で受け取る @RequestBody の変換時などに未知のプロパティが無視されるようになります。

解決策4:@JsonAnySetter を活用して未知のデータを保持する

単純に無視するのではなく、「定義はしていないが、送られてきたデータは後で参照したい」という特殊なケースでは、@JsonAnySetter を使用します。

実装例

Java
import com.fasterxml.jackson.annotation.JsonAnySetter;
import java.util.HashMap;
import java.util.Map;

class User {
    private String name;
    private Map<String, Object> extraProperties = new HashMap<>();

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    // 定義されていないプロパティがここに入ってくる
    @JsonAnySetter
    public void addExtraProperty(String key, Object value) {
        extraProperties.put(key, value);
    }

    public Map<String, Object> getExtraProperties() {
        return extraProperties;
    }
}

この手法を使えば、UnrecognizedPropertyException を回避しつつ、ログ出力やデバッグのために未知のプロパティを一時的に保持しておくことができます。

動的なスキーマを持つJSONを扱う場合に非常に強力なツールとなります。

開発におけるベストプラクティス

UnrecognizedPropertyException への対策を講じる際、どの手法を選択すべきかはシステムの要件によります。

ここでは、プロの開発者が実践している判断基準を紹介します。

1. 堅牢性(Robustness Principle)の適用

「送信するものに関しては厳密に、受信するものに関しては寛容に」というポステルの法則に従えば、受信側であるJavaアプリケーションは未知のプロパティを無視するように設定しておくべきです。

これにより、APIを提供する側が後方互換性を保ちながらフィールドを追加した際に、クライアント側がクラッシュするのを防ぐことができます。

2. クラス単位の設定を優先する

グローバル設定(FAIL_ON_UNKNOWN_PROPERTIES = false)は便利ですが、予期せぬ副作用を生むことがあります。

まずは @JsonIgnoreProperties(ignoreUnknown = true) を個別のDTOに適用することを検討してください。

特に、自システムが管理していない外部APIのレスポンスを受けるDTOには必須と言えます。

3. セキュリティへの配慮

未知のプロパティを無条件に受け入れることは、ごく稀に「Mass Assignment攻撃」のような脆弱性につながる懸念があります(不要なデータが意図せず内部のMapやオブジェクトにバインドされるなど)。

無視する設定自体は安全ですが、@JsonAnySetter でデータを保持する場合は、その内容をそのままデータベースの更新クエリなどに流用しないよう注意が必要です。

4. 開発環境でのデバッグ

本番環境では無視するように設定していても、開発環境のユニットテストなどでは、あえてデフォルトの「厳格モード」で実行し、JSONとDTOに乖離がないかチェックする手法もあります。

これにより、ドキュメントと実装のズレを早期に発見できます。

高度なトピック:Mix-in による外部ライブラリの制御

もし、対象のJavaクラスがサードパーティ製ライブラリに含まれており、直接アノテーション(@JsonIgnoreProperties)を付与できない場合は、Mix-in(ミックスイン)という機能を使用します。

実装例

Java
// 対象のクラス(編集不可と仮定)
class ExternalUser {
    public String name;
}

// ミックスイン用の抽象クラスまたはインターフェース
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class UserMixin {}

// ObjectMapperへの登録
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(ExternalUser.class, UserMixin.class);

このように、既存のクラスを汚染することなく、Jacksonの動作をカスタマイズすることが可能です。

大規模なプロジェクトや、多くの外部ライブラリに依存しているシステムでは非常に重宝するテクニックです。

まとめ

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException は、Jacksonのデフォルト設定が厳格であるために発生する例外です。

この問題を解決するには、以下の3つのアプローチが基本となります。

  • 特定クラスへの対策: @JsonIgnoreProperties(ignoreUnknown = true) を使用。
  • システム全体の対策: ObjectMapperFAIL_ON_UNKNOWN_PROPERTIESfalse に設定。
  • 特殊なデータの保持: @JsonAnySetter で未知のプロパティをキャッチ。

モダンな分散システムやWeb API開発において、「未知のフィールドを許容する柔軟性」は、システムの可用性とメンテナンス性を高めるための重要な要素です。

プロジェクトの性質に合わせて適切な設定を選択し、変更に強い堅牢なJavaアプリケーションを構築しましょう。

エラーが発生した際に単に「無視する」だけでなく、なぜそのデータが送られてきているのか、将来的にそのデータが必要になる可能性はあるのかを考慮することも、優れたエンジニアへの第一歩です。

この記事で紹介した手法を駆使して、Jacksonによるデータバインディングのトラブルをスマートに解決してください。