53 人が閲覧(直近 30 日) Java

JacksonをMavenで導入する手順|2.22と3.2の依存の違いとObjectMapper・JsonNodeの使い方

JacksonをMavenで導入する手順|2.22と3.2の依存の違いとObjectMapper・JsonNodeの使い方

JacksonはJavaでJSONを読み書きするライブラリです。Mavenならpom.xmlにjackson-databindを1つ書けば、残りの部品は自動で入ります。ただし2025年10月の3.0以降、座標もパッケージ名も違う2系と3系が並行して更新され、2026年9〜10月には両系統で深刻度Highの脆弱性修正が続きました。この記事では、2.22.4と3.2.4の依存の書き方、修正済みの版の選び方とSpring Bootでの上書き方法、2系と3系の既定動作の違い、ObjectMapperとJsonNodeの使い方を、Java 25での実行結果付きで説明します。

まとめ:JacksonをMavenで使うときの要点

  • 書く依存は1つ:jackson-databindを追加すれば、jackson-coreとjackson-annotationsは推移的依存で入る。
  • 2系と3系は座標が違う:2系はcom.fasterxml.jackson.core(2026年10月23日時点の最新2.22.4)、3系はtools.jackson.core(同3.2.4)。3系でもjackson-annotationsだけは2系の座標のまま使う。
  • 版の下限:2026年9月末〜10月1日に公表されたHighの勧告4件を避けるには、2.22.3・2.21.7・2.18.11・3.2.3・3.1.7以上が必要。
  • Spring Bootの管理版を確認する:Boot 3.5.16は2.21.4のままなので、jackson-bom.versionで上書きする。Boot 4.1.2は3.1.7と2.21.7を管理している。
  • 3系は既定動作が変わった:未知のプロパティを無視し、java.timeを標準で扱い、例外は非検査例外になる。2系のコードをパッケージ名だけ置き換えても同じ結果にはならない。

Jacksonを構成する3つのモジュールと役割

Jacksonは用途ごとにjarが分かれています。普段コードから直接使うのはjackson-databindのObjectMapperとJsonNodeで、残りの2つはその土台です。

artifactId 役割 主なクラス
jackson-core JSONを1トークンずつ読み書きする低水準API JsonParser、JsonGenerator
jackson-annotations 変換規則を指定するアノテーション @JsonProperty、@JsonIgnore
jackson-databind JavaオブジェクトやツリーとJSONの変換 ObjectMapper、JsonNode

databindはcoreとannotationsに依存しているため、databindだけを書けば3つが揃います。mvn dependency:treeで確かめた結果が次です。annotationsの版がパッチ番号の無い2.22になっているのは、2.20以降annotationsだけがマイナー版単位で出る方式に変わったためです。

demo:j2:jar:1
\- com.fasterxml.jackson.core:jackson-databind:jar:2.22.4:compile
   +- com.fasterxml.jackson.core:jackson-annotations:jar:2.22:compile
   \- com.fasterxml.jackson.core:jackson-core:jar:2.22.4:compile

2系の日付・時刻対応(jackson-datatype-jsr310)と、YAML・CSV対応(jackson-dataformat-*)は別モジュールです。3系のjava.time対応はdatabindに組み込まれています。追加する場合は版をBOMで揃えます(後述)。

pom.xmlへの追加:2系と3系の依存の書き方

Jackson 2系のMaven座標と対応Java

jackson-databindのREADMEには「Versions 2.13 and above require JDK 8」とあり、Java 8・11が残る環境でも使える系統です。

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.22.4</version>
</dependency>

パッケージはcom.fasterxml.jackson.databindです。検索でよく見かけるcom.fasterxml.jackson.databind.JsonNodeはこの系統のクラス名です。

Jackson 3系のMaven座標とannotationsの扱い

3系はgroupIdとJavaパッケージがどちらもtools.jacksonに変わり、READMEの要件は「Versions 3.x require JDK 17」です。

<dependency>
  <groupId>tools.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>3.2.4</version>
</dependency>

このときの依存ツリーでは、annotationsだけが2系の座標で入ります。

demo:j3:jar:1
\- tools.jackson.core:jackson-databind:jar:3.2.4:compile
   +- com.fasterxml.jackson.core:jackson-annotations:jar:2.22:compile
   \- tools.jackson.core:jackson-core:jar:3.2.4:compile

@JsonPropertyなどは2系と3系で同じクラスを共有します。注意したいのは、Maven Centralのjackson-annotationsのmaven-metadata.xmlでは、2026年10月23日に取得した時点でも<latest>と<release>が3.0-rc5を指していることです。これは正式版ではないRC(リリース候補)なので、「最新」を機械的に拾うツールで版を決めると誤ります。annotationsの版はdatabindの推移的依存かBOMに任せます。

jackson-bomによる関連モジュールのバージョン管理

databindに加えてjackson-datatype-jsr310などを入れる場合は、jackson-bomをdependencyManagementでimportし、各依存から<version>を省きます。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson</groupId>
      <artifactId>jackson-bom</artifactId>
      <version>2.22.4</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
  </dependency>
  <dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
  </dependency>
</dependencies>

3系のBOMはtools.jackson:jackson-bomです。Spring Bootを使っている場合はBootがJacksonの版を管理しているので、上書きの方法は後述の版の選び方の章を見てください。

Gradle Kotlin DSLでのJackson依存とBOMの指定

Gradle(Kotlin DSL)では座標を1行で書きます。BOMはplatform()で読み込みます。

dependencies {
    implementation(platform("com.fasterxml.jackson:jackson-bom:2.22.4"))
    implementation("com.fasterxml.jackson.core:jackson-databind")
    // 3系の場合
    // implementation("tools.jackson.core:jackson-databind:3.2.4")
}

Mavenを使わずjarファイルで導入する手順

ビルドツールが無い環境では、Maven Centralから3つのjarを取得してクラスパスに置きます。2系ならjackson-databind-2.22.4.jar(約1.7MB)、jackson-core-2.22.4.jar(約600KB)、jackson-annotations-2.22.jar(約84KB)です。libフォルダに置いた場合のコンパイルと実行は次のとおりです(Windowsは区切りを;にします)。

javac -cp "lib/*" BindExample.java
java -cp "lib/*:." BindExample

databindのjarだけを置くと依存が自動で入らず、パッケージcom.fasterxml.jackson.core.typeは存在しませんというコンパイルエラーになりました。3つの版を手で揃え続けることになるので、継続して使うならMavenかGradleに移します。

Jackson 2.22と3.2の違い:既定動作を実測した対照表

3系への移行はパッケージ名の置換だけでは終わりません。同じJSONと、比較項目に応じたrecord・Beanを2.22.4と3.2.4に渡し、Temurin 25で実行した結果を並べます。

項目 2.22.4 3.2.4
groupId/パッケージ com.fasterxml.jackson tools.jackson
必要なJava 8以上 17以上
例外の種類 検査例外(IOException系) 非検査例外(RuntimeException系)
recordに無い項目を含むJSON UnrecognizedPropertyException 無視して変換
intの項目にnull 0で変換 MismatchedInputException
LocalDateの書き出し InvalidDefinitionException “2026-10-23”
出力順(通常のクラス) 宣言順 アルファベット順
出力順(record) 宣言順 宣言順
入れ子の深さの上限 1,000 500
文字列1個の長さの上限 20,000,000 100,000,000
設定の変更 configure()で既存インスタンスを変更 builder()/rebuild()で新規作成
文字列値の取得 asText()/textValue() asString()/stringValue()

影響が大きいのは未知項目の扱いです。2系で想定外の項目を例外で検知していた設計は、3系では黙って通ります。同じ検知を続けたいなら、3系でFAIL_ON_UNKNOWN_PROPERTIESを明示的に有効にします。逆に、intの項目へのnullは2系だと0で素通りし、3系では例外になります。通常のクラスの出力順の変化は、JSON文字列をそのまま比較しているテストやAPIの署名計算を壊します。3系でアルファベット順の並べ替えを解除するにはMapperFeature.SORT_PROPERTIES_ALPHABETICALLYを無効にします。ただし宣言順は保証されないため、順序を固定する場合は@JsonPropertyOrderで項目順を指定します。

2系と3系はパッケージが違うため、同じクラスパスに共存させても衝突しません。2.22.4と3.2.4を同時に載せて両方のマッパーでJSONを読めることを確認しました。ライブラリが2系に依存したまま、アプリ側だけ3系へ移る段階的な移行ができます。

Jacksonのバージョンの選び方:LTS・脆弱性修正・Spring Bootの管理版

LTSと最新ブランチの違い

FasterXMLの公式リリース表(Jackson-Releases)では、2.21系・3.1系・2.18系がLTS(長期サポート)、2.22系と3.2系は最新ブランチでLTSではありません。2.21系は2028年1月31日まで開く予定と明記されている一方、2.18系は2026年末までの予定です。2.18系を使っているなら、今のうちに2.21系へ上げる計画を立てておくべきです。3.0系は2026年2月の3.1.0公開で終了しました。

  • 3系を選ぶ:Java 17以上で新規に作る場合、Spring Boot 4系を使う場合。Boot 4の2系用モジュールspring-boot-jackson2は非推奨扱いです。
  • 2系を選ぶ:Java 8・11が残る場合、Spring Boot 3系を使う場合、2系前提のライブラリを多く抱える場合。

2026年9〜10月の脆弱性勧告と修正済みの版

GitHub Advisory Databaseには、2026年9月30日と10月1日にjackson-databind・jackson-coreの深刻度Highの勧告が4件公表されました。前方参照の解決に二乗時間がかかる問題(CVE-2026-91777)、未知の型IDを保持し続ける問題(CVE-2026-91776)、数値判定の正規表現のReDoS(CVE-2026-89407)、エラー報告のトークン長の制限漏れ(CVE-2026-89425)です。修正を含む最小の版は系統ごとに次のとおりです。

系統 4件すべての修正を含む版 10月17〜18日の最新パッチ
2.22(最新ブランチ) 2.22.3 2.22.4
2.21(LTS) 2.21.7 2.21.8
2.18(LTS) 2.18.11 2.18.12
3.2(最新ブランチ) 3.2.3 3.2.4
3.1(LTS) 3.1.7 3.1.8

10月のパッチ(2.22.4のリリースノートなど)には、型IDの型パラメータと配列要素型の検証(CVE-2026-110879)も入っています。これは型情報をJSONから受け取る多態型の設定に関わる修正で、その危険性は安全でないデシリアライゼーションの仕組みと防御実装で扱っています。外部から受け取るJSONを読むアプリでは、系統内の最新パッチに上げておくのが安全です。

Spring Bootが管理するJacksonの版と上書き方法

Spring BootはJacksonの版を自分で決めて配ります。spring-boot-starter-jsonだけのプロジェクトでdependency:treeを取ると、次の版に解決されました。

Spring Boot 公開日 管理するJackson 上の勧告4件
4.1.2 2026-10-22 3.1.7(2系用は2.21.7) 修正済み
4.1.1 2026-08-20 3.1.5(2系用は2.21.5) 対象
3.5.16 2026-06-25 2.21.4 対象

Spring Boot 3.5系は公式のサポート表で無償サポートが2026年6月30日に終わっており、3.5.16より新しい無償版は出ていません。3.5系のまま運用するなら、Jacksonの版を自分で上書きする必要があります。spring-boot-starter-parentを親にしている場合は、プロパティを1行書けば足ります。

<properties>
  <!-- Boot 3.5系の例。Boot 4系で3系を上げるときも同じ名前で3.1.8などを指定する -->
  <jackson-bom.version>2.21.8</jackson-bom.version>
</properties>

3.5.16の親でこの値を指定すると、databind・core・jsr310などがそろって2.21.8に変わりました。Boot 4系で2系側を上げるときのプロパティ名はjackson-2-bom.versionです。

親POMを使わずspring-boot-dependenciesをdependencyManagementでimportしている場合、このプロパティは効きません。Boot 4.1.2のBOMをimportしたプロジェクトでjackson-bom.versionを3.1.8にしても、解決結果は3.1.7のままでした。この構成では、jackson-bomをspring-boot-dependenciesより前にimportします。そうすると3.1.8に変わりました。Spring Boot 4でのJackson 3対応の全体像はSpring Boot 4の変更点・新機能とSpring Boot 3との違いにまとめています。

ObjectMapperによるJSONとJavaオブジェクトの相互変換

型が決まっているJSONは、ObjectMapperでrecordやクラスへ直接変換できます。readValueで読み、writeValueAsStringで書きます。リストのように要素型が必要なものはTypeReferenceで型を渡します(以下は2系のコードで、recordを使うためJava 16以上が必要です)。

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

public class BindExample {
    // 変換用の型。record でもクラスでもよい
    public record User(String name, int age) {}

    // ObjectMapper は生成コストが高いので1つを使い回す
    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static void main(String[] args) throws Exception {
        // JSON文字列 → Javaオブジェクト
        User user = MAPPER.readValue("{\"name\":\"Sato\",\"age\":31}", User.class);
        System.out.println(user);

        // Javaオブジェクト → JSON文字列
        System.out.println(MAPPER.writeValueAsString(new User("Suzuki", 28)));

        // 配列はTypeReferenceで要素の型を渡す
        List<User> users = MAPPER.readValue(
            "[{\"name\":\"A\",\"age\":1},{\"name\":\"B\",\"age\":2}]",
            new TypeReference<List<User>>() {});
        System.out.println(users.size() + " " + users.get(1).name());
    }
}
User[name=Sato, age=31]
{"name":"Suzuki","age":28}
2 B

recordはアノテーション無しでコンストラクタ経由で変換されます。recordの仕組みはJavaのレコードクラス(record)の解説を参照してください。ObjectMapperをリクエストごとにnewすると、型ごとの変換器のキャッシュが毎回捨てられます。設定を済ませたものをstatic finalで共有するのが定石です。

Jacksonアノテーションによる項目名・除外・null出力の制御

JSONの項目名がJavaの名前と違う、パスワードのように書き出したくない項目がある、といった調整はjackson-annotationsのアノテーションで型に書きます。

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

public class AnnotationExample {
    // 未知の項目を無視し、nullの項目は書き出さない
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public record Account(
        @JsonProperty("user_id") long userId,   // JSON側の名前を指定
        String email,
        @JsonIgnore String password) {}         // 読み書きの対象から外す

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Account a = mapper.readValue(
            "{\"user_id\":7,\"email\":null,\"password\":\"x\",\"extra\":1}", Account.class);
        System.out.println(a);
        System.out.println(mapper.writeValueAsString(new Account(8, null, "secret")));
    }
}
Account[userId=7, email=null, password=null]
{"user_id":8}

JSONにpasswordがあっても読み込まれず、書き出しではpasswordと、nullのemailが消えています。アノテーションのパッケージは3系でもcom.fasterxml.jackson.annotationのままなので、3.2.4ではObjectMapperのimportをtools.jackson.databindに替えてthrowsを外すだけで同じ結果になりました。なお、recordの@JsonIgnoreが命名規則(PropertyNamingStrategy)との併用で効かなくなる不具合(CVE-2026-59888)があり、2.15.0以上2.18.8未満、2.19.0以上2.21.4未満、3.0.0以上3.1.4未満が対象です。Lombokの@Builderと組み合わせる場合の注意はJavaのBuilderパターン実装とLombok @Builderの使い方で扱っています。

JsonNodeによるJSONの解析と値の取得

項目が可変で、全体をメモリ上に保持できるJSONは、readTreeでJsonNodeのツリーにして辿ります。クラスを定義しなくて済むのが利点です。

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

public class TreeExample {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        String json = "{\"user\":{\"name\":\"Sato\",\"age\":31,\"tags\":[\"java\",\"maven\"]},\"score\":null}";
        JsonNode root = mapper.readTree(json);

        System.out.println(root.get("missing"));                        // null
        System.out.println(root.path("missing").isMissingNode());       // true
        System.out.println(root.get("score").isNull());                 // true(JSONのnull)

        JsonNode user = root.path("user");
        System.out.println(user.path("name").asText());                 // Sato
        System.out.println(user.path("age").asInt());                   // 31
        System.out.println(user.path("age").textValue());               // null(数値ノードなので)
        System.out.println(root.path("nope").path("deep").asInt(-1));   // -1(途中が無くても例外にならない)

        System.out.println(root.at("/user/tags/1").asText());           // maven
        for (JsonNode tag : user.path("tags")) {
            System.out.println("tag=" + tag.asText());
        }
    }
}

get()とpath()の違い

存在しないキーに対して、get()はJavaのnullを返し、path()はMissingNodeを返します。get()を連鎖させると途中でNullPointerExceptionになるので、深い階層を辿るときはpath()を使います。JSON側に"score":nullと書かれている場合は、get()でもnullではなくNullNodeが返り、isNull()がtrueになります。「キーが無い」と「値がnull」を区別したいときは、この2つを使い分けます。

asText()・textValue()・asInt()の戻り値

asText()やasInt()は値を変換しますが、既定値の扱いは異なります。2系のasInt(-1)は数値に変換できない場合に指定値を返します。一方、asText("fb")はMissingNodeやNullNodeでは指定値を返しますが、ObjectNodeやArrayNodeでは空文字を返します。引数なしのasText()はNullNodeに対して文字列の「null」を返します(2.22.4で確認)。textValue()は文字列ノードのときだけ値を返し、数値ノードではnullです。3系ではasText()とtextValue()がそれぞれasString()とstringValue()に改名され、stringValue()は数値ノードに対してnullではなくJsonNodeExceptionを投げます。3系のasText()は@Deprecated付きで残っているため、コンパイルは通り警告だけが出ます。

at()とJSON Pointerによる深い階層の値の取得

at("/user/tags/1")のように、RFC 6901のJSON Pointerで位置を1回で指定できます。配列は0始まりの添字で、存在しない位置ではMissingNodeが返ります。配列ノードはそのまま拡張for文で回せます。

ObjectNode・ArrayNodeによるJSONの編集と文字列化

読み込んだJsonNodeは参照用の型なので、編集するときはObjectNode(オブジェクト)やArrayNode(配列)にキャストします。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class EditExample {
    public record User(String name, int age) {}

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = (ObjectNode) mapper.readTree(
            "{\"user\":{\"name\":\"Sato\",\"age\":31,\"tags\":[\"java\"]},\"score\":null}");

        root.put("active", true);                       // 追加・上書き
        root.remove("score");                           // 削除
        ((ArrayNode) root.path("user").path("tags")).add("maven");  // 配列に追加
        root.putObject("meta").put("version", 2);       // 子オブジェクトを作って追加
        root.withObject("/audit/created").put("by", "batch");       // 途中の階層もまとめて作る

        System.out.println(mapper.writeValueAsString(root));
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root.path("meta")));

        // ツリーとJavaオブジェクトの相互変換
        ObjectNode userNode = (ObjectNode) root.path("user").deepCopy();
        userNode.remove("tags");                        // User に無い項目は外す(2系は未知項目で例外)
        User user = mapper.treeToValue(userNode, User.class);
        JsonNode back = mapper.valueToTree(user);
        System.out.println(user + " " + back);
    }
}
{"user":{"name":"Sato","age":31,"tags":["java","maven"]},"active":true,"meta":{"version":2},"audit":{"created":{"by":"batch"}}}
{
  "version" : 2
}
User[name=Sato, age=31] {"name":"Sato","age":31}

整形するならwriterWithDefaultPrettyPrinter()を使います。treeToValueはツリーをrecordへ、valueToTreeはオブジェクトをツリーへ変換します。tagsを外しているのは、2系の既定ではUserに無い項目があるとUnrecognizedPropertyExceptionになるためです。

jackson-coreのストリーミングAPIによる大容量JSONの逐次処理

readTreeで文書全体をツリー化したり、readValueで配列全体をListに変換したりすると、変換結果全体をメモリに保持します(ObjectMapperでもreadValuesを使えば要素ごとに読めます)。数百MBのログや巨大な配列から一部の値だけを集計したいときは、jackson-coreのJsonParserで1トークンずつ読みます。databindを使わず、jackson-core単体で動きます。

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.StringWriter;

public class StreamingExample {
    public static void main(String[] args) throws Exception {
        JsonFactory factory = new JsonFactory();
        String json = "{\"items\":[{\"id\":1,\"price\":120},{\"id\":2,\"price\":80}]}";

        // price だけを合計する(ツリーは作らないが、入力のString全体はメモリに保持する)
        long total = 0;
        try (JsonParser p = factory.createParser(json)) {
            JsonToken t;
            while ((t = p.nextToken()) != null) {
                if (t == JsonToken.FIELD_NAME && "price".equals(p.currentName())) {
                    p.nextToken();
                    total += p.getLongValue();
                }
            }
        }
        System.out.println("total=" + total);

        // 書き出しも1トークンずつ
        StringWriter w = new StringWriter();
        try (JsonGenerator g = factory.createGenerator(w)) {
            g.writeStartObject();
            g.writeNumberField("total", total);
            g.writeEndObject();
        }
        System.out.println(w);
    }
}
total=200
{"total":200}

例では短い文字列を渡していますが、大きなファイルはFiles.readStringなどで一括で読み込まず、createParserにFileやInputStreamを渡せば、入力全体をメモリに置かずに処理できます。3系では、JsonFactoryがtools.jackson.core.jsonパッケージへ移り、JsonToken.FIELD_NAMEはPROPERTY_NAME、writeNumberField()はwriteNumberProperty()に改名されています。この3点を書き換えた版が3.2.4で同じ結果を出しました。

例外処理と失敗しやすい3つのパターン

検査例外の2系と非検査例外の3系

2系のreadTreeやreadValueはJsonProcessingException(IOExceptionのサブクラス)を宣言しているため、catchかthrowsが必須です。3系ではJacksonExceptionがRuntimeExceptionを継承しており、ラムダやStreamの中でも包まずに呼べます。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ErrorExample {
    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            JsonNode node = mapper.readTree("{\"a\":1,}");  // 末尾カンマは不正なJSON
            System.out.println(node);
        } catch (JsonProcessingException e) {           // 2系は検査例外なのでcatchかthrowsが必須
            System.out.println(e.getClass().getSimpleName() + ": " + e.getOriginalMessage());
        }
    }
}
JsonParseException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name

3系で同じ入力を渡すと、例外クラスはtools.jackson.core.exc.StreamReadExceptionになり、メッセージの「field name」も「property name」に変わります。例外メッセージの文字列で分岐しているコードは、移行時に見直しが必要です。

jackson-coreの版ずれで起きるNoClassDefFoundError

databindとcoreの版が食い違うと、コンパイルは通るのに実行時に落ちます。databind 2.22.4に対してcore 2.12.7を直接指定したpomで試すと、Mavenは直接指定した2.12.7を優先し、最初のJSON変換で次の例外が出ました。

Exception in thread "main" java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/exc/StreamConstraintsException
	at com.fasterxml.jackson.databind.deser.std.JdkDeserializers.<clinit>(JdkDeserializers.java:31)

古いcoreを引く別ライブラリが依存ツリーの浅い位置にあるとこの状態になります。mvn dependency:tree -Dincludes=com.fasterxml.jackson.coreで版を確認し、ずれていればjackson-bomで揃えます。

入れ子の深さと文字列長の上限

Jacksonは2.15以降、巨大な入力による資源枯渇を防ぐため読み込みに上限を設けています。既定値をStreamReadConstraints.defaults()で読むと、2.22.4は入れ子1,000段・文字列2,000万文字、3.2.4は入れ子500段・1億文字でした。3.2.4に1,000段の配列を渡すと「Document nesting depth (501) exceeds the maximum allowed (500…)」で失敗します。2系で動いていた深いJSONが3系で読めなくなる場合は、次のように上限を明示します(2系の例。1,500段の配列が読めることを確認済み)。

JsonFactory factory = JsonFactory.builder()
    .streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(2000).build())
    .build();
ObjectMapper deep = new ObjectMapper(factory);

上限は外さず、入力の深さと使えるメモリに合わせた有限値にします。前章の勧告の多くは、この種の上限をすり抜けて計算量やメモリを食わせる問題でした。

Jackson 3系のJsonMapper構築とLocalDateの変換

3系ではJsonMapper.builder()で設定を決めてから生成し、生成後は変更できません。設定違いが欲しいときはrebuild()で別インスタンスを作ります。2系の挙動に寄せたい設定も、ここで明示します。

import tools.jackson.core.JacksonException;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
import java.time.LocalDate;

public class Jackson3Example {
    public record Event(String title, LocalDate date) {}

    public static void main(String[] args) {   // 3系は非検査例外なので throws 不要
        JsonMapper mapper = JsonMapper.builder()
            .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)  // 2系と同じく未知項目を例外にする
            .build();

        // java.time は追加モジュールなしで ISO 形式になる
        System.out.println(mapper.writeValueAsString(new Event("release", LocalDate.of(2026, 9, 27))));

        JsonNode root = mapper.readTree("{\"name\":\"Sato\",\"age\":31}");
        System.out.println(root.path("name").asString());   // 2系の asText() に相当

        // 設定を変えるときは rebuild() で別インスタンスを作る
        JsonMapper lenient = mapper.rebuild()
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .build();
        System.out.println(lenient.readValue("{\"title\":\"a\",\"date\":\"2026-01-01\",\"x\":1}", Event.class));

        try {
            mapper.readValue("{\"title\":\"a\",\"date\":\"2026-01-01\",\"x\":1}", Event.class);
        } catch (JacksonException e) {
            System.out.println(e.getClass().getSimpleName());
        }
    }
}
{"title":"release","date":"2026-09-27"}
Sato
Event[title=a, date=2026-01-01]
UnrecognizedPropertyException

2系でLocalDateを扱うにはjackson-datatype-jsr310を追加してJavaTimeModuleを登録します。ただし2系は登録しただけだと[2026,9,27]という配列で書き出し、"2026-09-27"にするにはSerializationFeature.WRITE_DATES_AS_TIMESTAMPSの無効化が別に必要です。3系はこの2手間が不要です。2系から3系への移行を含むJavaシステムの改修は、一創のフルスクラッチ開発でもご相談いただけます。

よくある質問

jackson-coreやjackson-annotationsも依存に書くべきですか?

通常は書きません。jackson-databindだけで推移的に入ります。版を固定したい事情があるなら、個別に書くよりjackson-bomをimportしてください。個別指定はcoreだけ古い版になる事故の元です。

ObjectMapperはスレッドセーフですか?

2系のJavadocには「最初の読み書きの前に全ての設定を終えていればスレッドセーフ」と書かれています。3系のマッパーは生成後に設定を変えられないので、この制約を意識する必要がありません。

Spring Bootでは自分でJacksonを追加する必要がありますか?

spring-boot-starter-webなどのスターターに含まれるので、追加は不要です。Spring Bootは自前のObjectMapper(4系ではJsonMapper)をBeanとして用意し、spring.jackson.*のプロパティで設定を変えられます。版だけは前述のとおり確認してください。依存の書き方はSpring Bootの4.1系の依存名と最小Web APIの作り方にまとめています。

JsonNodeのtoString()とwriteValueAsString()は同じ結果になりますか?

2.22.4と3.2.4では、エスケープを含め同じ正しいJSON文字列を返しました。整形したい場合は2系のtoPrettyString()かwriterWithDefaultPrettyPrinter()を使います。ObjectMapperに独自の設定(出力時の数値形式など)を入れているときは、その設定を通るwriteValueAsString()を使うほうが確実です。

Mavenでjackson-databindの最新版を確認する方法は?

Maven Centralのmaven-metadata.xmlの<release>を見ます。2026年10月23日時点でdatabindは2系が2.22.4、3系が3.2.4でした。ただしannotationsは<release>自体がRC版を指しているため、databindの値を見てください。LTSかどうかはFasterXMLの公式リリース表で確認します。JDKの版を切り替えて試す場合はSDKMAN!によるJavaのインストールと切り替えが便利です。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.11.03 テックブログ クラブツーリズム不正アクセス:約432万件の対象確認と偽メールの見分け方・流出検知の設計
  2. 2026.11.04 テックブログ CVE-2026-119977:WordPress更新中のRCEと安全な更新手順
  3. 2026.10.29 テックブログ ソニー生命の情報漏洩38.5万件|ログインできない理由、再開状況と窓口の電話番号
  4. 2026.11.02 テックブログ アララの不正アクセス:メール約7,700万件の持ち出しと契約企業が行うAPIキー再設定・代替送信・連名報告
  5. 2026.11.03 テックブログ ノジマ不正アクセスで約700万件漏えい:対象確認・窓口と乗っ取り注文を止める実装

RELATED POSTS 関連記事

目次