Spring Boot 2.7 + Java 17 환경에서 개발하고, Tomcat 8 + Java 17 (내부적으로는 Java 8) 환경에서 빌드를 시도하니 다음과 같은 에러가 발생했다.
[2025-04-17 09:35:12.569] [ERROR] [ ajp-nio-0.*.*.0-8019-exec-42 ] c.s.a.c.w.c.e.ErrorHandler.handleException:26 - 에러 발생: Type definition error: [simple type, class com.skmns.aisc.calladmin.web.combination.adminUser.dto.CombinationResetPwReq]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.skmns.aisc.calladmin.web.combination.adminUser.dto.CombinationResetPwReq (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 2]
위 에러 메시지에서 핵심 메시지는 이 내용이다.
Cannot construct instance of `...CombinationResetPwReq` (no Creators, like default constructor, exist)
Java 8에서 @Builder를 사용한 DTO 클래스가 Default 생성자 없이 Jackson에서 역직렬화(Deserialization)되려 할 때 발생하는 InvalidDefinitionException 에러이다.
원인 분석
먼저 에러가 발생한 DTO의 코드를 보면, Builder 패턴을 사용하지만 생성자가 없다.
/**
* 패스워드 초기화 요청 DTO
*/
@Getter
@Setter
@Builder
public class CombinationResetPwReq implements Serializable {
private static final long serialVersionUID = 7708572632106740365L;
private String id;
private String pw;
@Builder.Default
private int pwdCnt = 0;
}
Jackson은 JSON Request Body를 위의 DTO 객체로 변환하려 했는데,
- DTO 클래스에는 기본 생성자(Default Constructor)가 없고
- Jackson이 쓸 수 있는 @JsonCreator 생성자도 존재하지 않으며
- @Builder는 컴파일 시에 별도의 Builder 클래스르 만들어서 사용하므로
→ Jackson 입장에서는 생성 경로가 없는 것과 같은 문제이다.
Java 17에서는 일부 라이브러리나 설정에서 Record나 Builder 기반 클래스의 역직렬화를 자동으로 처리하도록 개선된 반면, Java 8에서는 명시적으로 처리해주지 않으면 에러가 나는 버전 차이인 거로 확인되었다...
역직렬화(Deserialization)란?
직렬화(Serialization)는 Java 객체를 → JSON으로 변환하는 것,
역직렬화(Deserialization)는 JSON을 → Java 객체로 변환하는 것.
역직렬화(Deserialization)는 외부에서 전달된 데이터(JSON, XML 등)를 Java 클래스의 인스턴스로 만드는 과정으로, 아래 JSON을 HTTP 요청 본문으로 받았을 때, Spring Boot의 Controller가 @RequestBody를 통해 역직렬화 수행
{
"id": "admin123",
"pw": "securePass",
"pwdCnt": 3
}
@PostMapping("/resetPw")
public ResponseEntity<?> resetPassword(@RequestBody CombinationResetPwReq req) {
// req는 JSON → CombinationResetPwReq로 역직렬화된 객체
...
}
내부적으로는 Jackson과 같은 라이브러리가 JSON 데이터를 DTO 클래스의 인스턴스로 변환한다.
해결 방법
1. 기본 생성자 추가
NoArgsConstructor 어노테이션을 사용하거나, 직접 기본 생성자를 추가하는 방법이다. @Builder만 사용할 경우 생성자가 하나도 없기 때문에 Java 8에서는 Jackson이 기본 생성자를 찾지 못해 명시적으로 추가해 주어야 한다.
@Getter
@Setter
@Builder
@NoArgsConstructor // <- 이거 추가!
@AllArgsConstructor // @Builder 사용 시 NoArgs~만 추가하면 오류남
public class CombinationResetPwReq implements Serializable {
private static final long serialVersionUID = 7708572632106740365L;
private String id;
private String pw;
@Builder.Default
private int pwdCnt = 0;
}
2. @Jacksonized 사용
@Jacksonized는 Lombok(1.18.12 버전 이상)이 제공하는 어노테이션으로, @Builder와 함께 사용하면 빌더 기반으로 객체를 Jackson이 역직렬화할 수 있도록 하는 설정이다.
Jackson은 Builder 패턴을 이해하지 못하기 때문에, Java 8 버전에서는 @JsonPOJOBuilder나 @JsonDeserialize(builder=...)와 같은 어노테이션을 추가로 사용해야 한다. 하지만 @Jacksonized를 사용하면 Lombok이 이런 코드를 자동으로 생성한다.
- @JsonPOJOBuilder(withPrefix="")
- @JsonDeserialize(builder=MyClass.MyClassBuilder.class)
→ 즉, 내부적으로 아래 코드가 자동으로 생성되는 것
@JsonDeserialize(builder = CombinationResetPwReq.CombinationResetPwReqBuilder.class)
public class CombinationResetPwReq {
...
@JsonPOJOBuilder(withPrefix = "")
public static class CombinationResetPwReqBuilder {
}
}
@Jacksonized는 다음과 같이 사용할 수 있다. [최종 결정안]
@Getter
@Setter
@Builder
@Jacksonized // 역직렬화를 위해 추가
public class CombinationResetPwReq implements Serializable {
private String id;
private String pw;
@Builder.Default
private int pwdCnt = 0;
}
'⚙ Framework > Spring-스프링' 카테고리의 다른 글
[Spring] JPA Enum Converter로 DB Entity Mapping하기 (0) | 2025.03.25 |
---|---|
[Spring] 운영 환경에서의 로그 모니터링 레벨 구분 (0) | 2025.02.07 |
[Spring] 스프링 부트(Spring Boot) 프로젝트 생성 (0) | 2024.12.01 |
[E] No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.1.4 was found. (0) | 2023.10.16 |
[Java] Spring이란? (2) - Spring의 주요 특징 (0) | 2023.08.03 |