Lỗi 'No Creators' của Jackson: Cách khắc phục InvalidDefinitionException

beginner Java2026-07-28| Java (JDK 8+), Jackson Databind 2.x, Spring Boot, Lombok

Error Message

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.dto.UserDTO` (no Creators, like default constructor, exist)
#java#jackson#json#giải tuần tự hóa#lombok

Giải mã lỗi

Jackson là tiêu chuẩn công nghiệp cho việc xử lý JSON trong Java, nhưng nó nổi tiếng là khắt khe về cách khởi tạo các đối tượng. Nếu bạn thấy stack trace sau, Jackson đang báo cho bạn biết rằng nó không biết cách khởi tạo class của bạn:

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]

Nguyên nhân gốc rễ

Jackson thường cần một no-argument constructor (constructor không tham số) để tạo một đối tượng trống trước khi điền dữ liệu vào thông qua reflection hoặc các setter. Lỗi này thường xuất hiện trong bốn trường hợp cụ thể:

  • Thiếu Constructor mặc định: Bạn đã thêm một custom constructor có tham số. Trong Java, khi bạn định nghĩa bất kỳ constructor nào, trình biên dịch sẽ ngừng tự động cung cấp constructor mặc định không tham số.
  • Xung đột Lombok: Bạn đang sử dụng @Builder hoặc @AllArgsConstructor, điều này làm ẩn đi constructor mặc định mà Jackson yêu cầu.
  • Phạm vi Inner Class: DTO của bạn là một inner class nhưng thiếu từ khóa static. Jackson không thể khởi tạo một inner class không tĩnh mà không có tham chiếu đến outer class.
  • Tính bất biến (Immutability): Bạn đánh dấu các field là final, nhưng không chỉ cho Jackson cách ánh xạ các key JSON vào các tham số constructor của bạn.

Các giải pháp thực tế

1. Khôi phục No-Argument Constructor

Thêm một constructor thủ công là cách khắc phục nhanh nhất. Ngay cả khi nó trống, Jackson vẫn có thể sử dụng nó để khởi động quá trình tạo đối tượng.

public class UserDTO {
    private Long id;
    private String name;

    // Cần thiết cho việc giải tuần tự hóa (deserialization) của Jackson
    public UserDTO() {}

    public UserDTO(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

2. Cấu hình Lombok cho Jackson

Lombok là một công cụ tuyệt vời, nhưng nó thường gây ra lỗi cụ thể này. Nếu bạn sử dụng @Data hoặc @Builder, bạn phải thêm @NoArgsConstructor một cách rõ ràng. Đối với các class có các field final, hãy sử dụng thuộc tính force để khởi tạo các field đó về giá trị mặc định (null, 0, false) trong quá trình khởi tạo.

import lombok.*;

@Data
@NoArgsConstructor(force = true) // Tạo constructor mặc định mà Jackson cần
@AllArgsConstructor
@Builder
public class UserDTO {
    private final Long id;
    private final String name;
}

3. Xử lý các đối tượng bất biến với @JsonCreator

Nếu bạn ưu tiên các DTO bất biến nghiêm ngặt, bạn không cần constructor không tham số. Thay vào đó, hãy chỉ định Jackson sử dụng constructor hiện có của bạn bằng @JsonCreator. Bạn cũng phải gắn nhãn từng đối số bằng @JsonProperty để Jackson biết key JSON nào ánh xạ tới tham số nào.

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. Kích hoạt Java 8 Parameter Names

Việc viết @JsonProperty cho mọi field rất tẻ nhạt. Nếu bạn đang sử dụng Java 8 trở lên, bạn có thể biên dịch code của mình với flag -parameters. Điều này cho phép Jackson thấy tên thực tế của các đối số constructor lúc runtime. Để thực hiện điều này, hãy đăng ký ParameterNamesModule trong cấu hình ObjectMapper của bạn.

ObjectMapper mapper = new ObjectMapper()
    .registerModule(new ParameterNamesModule())
    .registerModule(new Jdk8Module());

// Giờ đây Jackson có thể tìm thấy constructor mà không cần thêm annotation
UserDTO user = mapper.readValue(jsonString, UserDTO.class);

5. Static Inner Classes

Luôn kiểm tra các nested class của bạn. Nếu UserDTO nằm bên trong một class khác, nó phải là static. Nếu không có điều này, Jackson sẽ cần một instance của class cha tồn tại trước, điều này là không thể trong quá trình phân tích cú pháp JSON tiêu chuẩn.

Xác minh lỗi đã được khắc phục

Đừng đoán; hãy kiểm tra. Sử dụng một JUnit test case đơn giản để đảm bảo DTO của bạn tương thích với Jackson. Điều này ngăn ngừa các lỗi hồi quy khi các thành viên trong nhóm cập nhật annotation Lombok sau này.

@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());
}

Danh sách kiểm tra phòng ngừa

  • Tiêu chuẩn hóa: Hãy đặt @NoArgsConstructor làm thành phần mặc định cho tất cả các DTO trong dự án của bạn.
  • Xác thực: Sử dụng một JSON Formatter để đảm bảo đầu vào của bạn không bị sai định dạng. Một dấu ngoặc bị thiếu đôi khi có thể khiến Jackson nhận diện sai cấu trúc đối tượng.
  • Kotlin: Nếu dự án của bạn sử dụng Kotlin, hãy luôn bao gồm jackson-module-kotlin. Nó tự động xử lý việc thiếu constructor mặc định trong các data class.

Related Error Notes