エラーの解読
JacksonはJavaにおけるJSON処理の業界標準ですが、オブジェクトの構築方法については非常に厳格であることで知られています。以下のスタックトレースが表示された場合、Jacksonはクラスのインスタンス化の方法がわからないと伝えています。
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.dto.UserDTO` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"id":1,"name":"John"}"; line: 1, column: 2]
根本原因
Jacksonは通常、リフレクションやセッターを介してデータを入力する前に、空のオブジェクトを作成するための引数なしのコンストラクタ(デフォルトコンストラクタ)を必要とします。このエラーは通常、以下の4つの特定のシナリオで発生します。
- デフォルトコンストラクタの欠如: 引数付きのカスタムコンストラクタを追加した場合。Javaでは、コンストラクタを1つでも定義すると、コンパイラはデフォルトの引数なしコンストラクタを自動的に提供しなくなります。
- Lombokの競合:
@Builderや@AllArgsConstructorを使用している場合。これらはJacksonが必要とするデフォルトコンストラクタを隠してしまいます。 - インナークラスのスコープ: DTOがインナークラスでありながら、
staticキーワードが欠落している場合。Jacksonは外部クラスの参照なしに、非静的インナークラスをインスタンス化することはできません。 - 不変性(Immutability): フィールドを
finalとしてマークしたが、JSONのキーをコンストラクタのパラメータにマッピングする方法をJacksonに伝えていない場合。
具体的な解決策
1. 引数なしコンストラクタを復元する
手動でコンストラクタを追加するのが最も手っ取り早い修正方法です。たとえ中身が空であっても、Jacksonはそれを利用してオブジェクト生成プロセスを開始できます。
public class UserDTO {
private Long id;
private String name;
// Jacksonのデシリアライズに必要
public UserDTO() {}
public UserDTO(Long id, String name) {
this.id = id;
this.name = name;
}
}
2. Jackson用にLombokを設定する
Lombokは優れたツールですが、しばしばこの特定のエラーの原因となります。@Dataや@Builderを使用する場合は、明示的に@NoArgsConstructorを追加する必要があります。finalフィールドを持つクラスの場合は、force属性を使用して、インスタンス化の際にそれらのフィールドをデフォルト値(null、0、false)で初期化するようにします。
import lombok.*;
@Data
@NoArgsConstructor(force = true) // Jacksonが必要とするデフォルトコンストラクタを作成
@AllArgsConstructor
@Builder
public class UserDTO {
private final Long id;
private final String name;
}
3. @JsonCreatorで不変オブジェクトを処理する
厳密に不変(immutable)なDTOを好む場合は、引数なしコンストラクタは不要です。代わりに、@JsonCreatorを使用してJacksonに既存のコンストラクタを指示します。また、JacksonがどのJSONキーをどのパラメータにマッピングするかを認識できるように、各引数に@JsonPropertyを付ける必要があります。
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class UserDTO {
private final Long id;
private final String name;
@JsonCreator
public UserDTO(@JsonProperty("id") Long id, @JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
}
4. Java 8のパラメータ名(Parameter Names)を有効にする
すべてのフィールドに@JsonPropertyを記述するのは面倒です。Java 8以降を使用している場合は、-parametersフラグを付けてコードをコンパイルできます。これにより、Jacksonは実行時にコンストラクタ引数の実際の名前を確認できるようになります。これを機能させるには、ObjectMapperの設定でParameterNamesModuleを登録してください。
ObjectMapper mapper = new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module());
// これでJacksonは追加のアノテーションなしでコンストラクタを見つけることができます
UserDTO user = mapper.readValue(jsonString, UserDTO.class);
5. 静的インナークラス
ネストされたクラスを常に確認してください。UserDTOが別のクラスの内部にある場合は、staticである必要があります。これがないと、Jacksonはまず親クラスのインスタンスが存在することを必要としますが、これは標準的なJSONパース中には不可能です。
修正の検証
推測するのではなく、テストしてください。簡単なJUnitテストケースを使用して、DTOがJacksonと互換性があることを確認します。これにより、後でチームメンバーがLombokアノテーションを更新した際のデグレード(先祖返り)を防ぐことができます。
@Test
void verifyDeserialization() throws Exception {
String json = "{\"id\":101, \"name\":\"Jane Doe\"}";
UserDTO result = new ObjectMapper().readValue(json, UserDTO.class);
assertNotNull(result);
assertEquals(101L, result.getId());
}
防止策チェックリスト
- 標準化: プロジェクト内のすべてのDTOに
@NoArgsConstructorをデフォルトで追加するようにします。 - バリデーション: JSON Formatterを使用して、入力が不正でないか確認してください。ブラケット(括弧)の閉じ忘れなどが原因で、Jacksonがオブジェクト構造を誤認することがあります。
- Kotlin: プロジェクトでKotlinを使用している場合は、必ず
jackson-module-kotlinを含めてください。これにより、データクラスにおける引数なしコンストラクタの欠如が自動的に処理されます。

