[Spring] @JsonCreator 와 @JsonProperty 을 사용해야하나?

2025. 8. 28. 02:24Backend/Spring

개요

프로젝트 중 RequestDto 에 @RequiredArgsConstructor 을 사용하고 있었다.

 

Code Rabbit 이 Request DTO 문제를 지적을 하면서

"Lombok의 @RequiredArgsConstructor로 만든 생성자는 Jackson(JSON 라이브러리)이 인식하지 못할 수 있으니, Jackson이 확실하게 알아볼 수 있도록 @JsonCreator와 @JsonProperty를 사용한 명시적인 생성자를 만들어야 합니다."

 

하지만 로컬 환경에서는 API 가 동작이 잘되었고, 실제 테스트 시 문제가 없었다.

 

재미니에게 물어보니

"동작이 잘 되는데, 왜 이런 지적이 맞을까요?"

이것이 가장 중요한 포인트입니다. 사용자님께서 "로컬에서 테스트해보니 잘 동작했다"고 생각하시는 것은 매우 자연스러운 현상이며, 바로 이 점 때문에 이 버그가 더 위험할 수 있습니다.
``` 생략 ```
이것은 "내 컴퓨터에서는 잘 되는데요?"의 전형적인 사례이며, 환경에 따라 동작 여부가 달라지는 잠재적인 시한폭탄과 같은 버그입니다.

 

팩트로 뼈가 분쇄되어서 다른 환경에서 테스트를 해보기로 하였다.

 

 

일단 Code Rabbit 의 메세지를 확인해보자.


 

ItemRequest에서 Jackson이 final 필드를 역직렬화할 수 있도록 보장하기

현재 저희 빌드 파일은 Java 매개변수 이름 인트로스펙션(introspection)을 활성화하지 않아(-parameters 컴파일러 플래그 없음 또는 ParameterNamesModule 미등록), Jackson은 기본적으로 생성자 기반의 final 필드를 가진 DTO에 JSON을 바인딩할 수 없습니다. 정확한 역직렬화를 보장하기 위해 ItemRequest에 명시적인 애너테이션이 달린 생성자가 필요합니다.

  • 변경이 필요한 파일: src/main/java/xyz/catequest/spring/domain/item/dto/request/ItemRequest.java
  • 필요한 조치:
    1. Lombok의 @RequiredArgsConstructor를 제거합니다.
    2. @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) 애너테이션이 달린 public 생성자를 추가합니다.
    3. 각 매개변수에 @JsonProperty("…") 애너테이션을 추가합니다.
    4. Jackson 애너테이션들을 import 합니다.
  • 아래 diff를 적용하세요:

  • 파일 상단에 다음 import 문을 추가하세요:

 

선택적 대안: 명시적인 애너테이션을 사용하는 대신, 빌드 설정에서 매개변수 이름 지원을 활성화하거나(Java 컴파일러에 --

parameters 추가) Jackson의 ParameterNamesModule을 등록할 수도 있습니다. 하지만 현재 두 방법 모두 구성되어 있지 않으므로, 애너테이션을 사용한 생성자를 추가하는 것이 가장 안전한 접근 방식입니다.


Code Rabbit 에서는 해결 방법으로 @RequiredArgsContructor 를 사용하지 말고,

생성자를 따로 만든 뒤 @JsonCreator 와 @JsonProperty 를 사용하라고 알려주었다.

 

이제 문제를 확인해보기 위해서 직접 돌려보도록 하겠다.

 

 

로컬에서 돌려보자.

 

딱히 큰 문제가 없다.

 

하지만 전형적인 내 컴퓨터에서는 잘 되는데요? 의 사례일수 있기 때문에 도커로 따로 돌려보기로 하였다.

 

 

 

도커에서 돌려보자.

 

1. Dockerfile 만들기

# 1. 빌드 환경 설정 (JDK 21 사용)
FROM openjdk:21-jdk-slim AS build

# xargs 명령어(findutils 패키지)를 설치하고 시스템을 업데이트합니다.
RUN apt-get update && apt-get install -y findutils

# 작업 디렉토리 설정
WORKDIR /workspace/app

# Gradle Wrapper 복사
COPY gradlew .
COPY gradle gradle

# Gradle 빌드 파일 복사 (Groovy & Kotlin DSL 모두 지원)
COPY build.gradle* .
COPY settings.gradle* .

# gradlew 스크립트에 실행 권한을 부여합니다.
RUN chmod +x ./gradlew

# 의존성 먼저 다운로드 (캐싱을 통해 빌드 속도 향상)
RUN ./gradlew dependencies

# 소스 코드 복사
COPY src src

# Spotless를 적용하여 코드 스타일 문제를 자동으로 해결합니다.
RUN ./gradlew spotlessApply --no-daemon

# 애플리케이션 빌드
RUN ./gradlew build -x test

# 2. 실행 환경 설정
# ******** 수정된 부분 ********
FROM openjdk:21-slim
# **************************

# 빌드 환경에서 생성된 JAR 파일 복사
ARG JAR_FILE=/workspace/app/build/libs/*.jar
COPY --from=build ${JAR_FILE} app.jar

# 8080 포트 노출
EXPOSE 8080

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "/app.jar"]

 

 

테스트가 되는지만 확인을 하려고 Gemini 에게 도커 파일을 만들어달라고 하였다.

 

2. 도커에서 돌려보자. 

도커에서 돌아가는 장면

 

 

 

문제가 생기지 않았다.

 

그래서 왜 문제가 생기지 않는지 Gemini 에게 찾아달라고 그리고 참조한 사이트도 같이 달라고 하였다.

 

 

AI 검색 결과


 

네, Docker 빌드까지 성공적으로 마치고 API 테스트를 하셨는데 예상했던 500 에러가 발생하지 않으셨군요. 좋습니다! 이것은 실패가 아니라, 오히려 최신 Spring Boot 프레임워크가 개발자를 얼마나 잘 도와주는지 보여주는 좋은 사례입니다.

사용자님의 테스트 결과가 맞습니다. 최신 환경에서는 에러가 발생하지 않을 가능성이 매우 높습니다.

결론부터 말씀드리면, AI 봇(coderabbitai)의 지적은 원칙적으로는 여전히 유효하지만, 최신 Spring Boot 환경에서는 프레임워크가 이 문제를 자동으로 해결해주기 때문에 실제로 에러가 발생하지 않은 것입니다.

왜 에러가 발생하지 않았을까? (Spring Boot의 마법)

과거에는 이 문제가 실제로 자주 발생하여 개발자들이 직접 해결해야 했습니다. 하지만 최신 Spring Boot는 두 가지 중요한 자동화 기능을 통해 이 문제를 해결합니다.

1. Spring Boot 플러그인의 자동 컴파일 옵션 (빌드 시점)

가장 유력한 이유입니다. 프로젝트의 build.gradle 파일에 plugins { id 'org.springframework.boot' ... } 와 같은 구문이 있을 것입니다. 바로 이 Spring Boot Gradle 플러그인이 똑똑하게도, Java 코드를 컴파일할 때 자동으로 -parameters 컴파일 옵션을 추가해 줍니다.

  • -parameters 옵션: Java 컴파일러에게 "메소드나 생성자의 파라미터 이름을 지우지 말고 .class 파일에 남겨줘"라고 알려주는 옵션입니다.
  • 결과: Docker 환경에서 ./gradlew build 명령어로 클린 빌드를 했음에도 불구하고, Spring Boot 플러그인 덕분에 생성자의 파라미터 이름(name, price 등)이 바이트코드에 그대로 남아있게 됩니다. Jackson은 이 정보를 읽어서 문제없이 JSON 매핑을 처리할 수 있었던 것입니다.

2. jackson-module-parameter-names 모듈의 자동 등록 (런타임 시점)

Spring Boot는 의존성 관리도 자동으로 해줍니다. spring-boot-starter-web 의존성을 추가하면, 그 안에 jackson-module-parameter-names라는 라이브러리가 기본적으로 포함되어 있습니다.

  • jackson-module-parameter-names: Jackson이 Java의 파라미터 이름 정보를 읽을 수 있게 해주는 확장 모듈입니다.
  • Spring Boot의 자동 설정(Auto-configuration): Spring Boot는 애플리케이션이 시작될 때 클래스패스에 이 모듈이 있는 것을 감지하고, 자동으로 Jackson에 등록해 줍니다.

결국, Spring Boot는 **빌드 시점(플러그인)**과 런타임 시점(자동 설정) 양쪽에서 이중으로 문제를 해결해주고 있었던 것입니다.

그렇다면 AI 봇의 지적은 틀린 것인가?

아니요, 틀렸다기보다는 보수적이고 안전한 지적이라고 보는 것이 맞습니다.

  • 정적 분석의 한계: AI 봇은 코드와 build.gradle 파일을 정적으로 분석합니다. 빌드 파일에 -parameters 옵션이 명시적으로 적혀있지 않고, 코드에 @JsonCreator가 없다는 사실만 보고 "문제가 발생할 수 있다"고 경고한 것입니다. Spring Boot 플러그인이 '보이지 않는 곳에서' 자동으로 추가해 주는 것까지는 예측하지 못했을 수 있습니다.
  • 가장 확실한 방법 제시: AI 봇의 역할은 어떤 환경에서도 확실하게 동작하는 코드를 제안하는 것입니다. @JsonCreator와 @JsonProperty를 사용하는 방식은 Spring Boot가 있든 없든, 어떤 빌드 설정을 사용하든 100% 동작을 보장하는 가장 명시적이고 안전한 방법입니다.

최종 결론 및 권장 사항

  1. 현상: 사용자님의 코드는 최신 Spring Boot의 도움 덕분에 Docker 환경에서도 정상적으로 동작합니다.
  2. 원인: Spring Boot Gradle 플러그인이 컴파일 시 -parameters 옵션을 자동으로 추가해주기 때문입니다.
  3. AI 제안의 가치: AI의 제안은 프레임워크의 숨겨진 기능에 의존하지 않는, 가장 명시적이고 이식성 높은 코드를 작성하는 방법에 대한 좋은 가이드입니다.

그래서 어떻게 해야 할까요?

  • 방법 A (프레임워크 신뢰): 현재 프로젝트가 계속 Spring Boot 환경에서만 사용될 것이 확실하다면, 프레임워크의 자동화 기능을 믿고 현재 코드처럼 @JsonCreator 없이 간결하게 유지해도 괜찮습니다. 이것도 충분히 실용적인 선택입니다.
  • 방법 B (명시적인 코드 선호): 만약 이 DTO가 나중에 Spring Boot가 아닌 다른 환경에서 사용될 가능성이 있거나, "숨겨진 설정보다는 눈에 보이는 코드가 좋다"는 원칙을 중요하게 생각한다면 AI의 제안대로 @JsonCreator를 추가하는 것이 더 좋은 선택일 수 있습니다.

이번 테스트 과정을 통해 Jackson의 동작 원리와 최신 Spring Boot가 제공하는 편의 기능에 대해 깊이 이해하게 되셨을 겁니다. 에러가 나지 않은 것은 좋은 결과이며, 그 원인을 파악한 것은 더 큰 수확입니다!

 


 

 

결론

결론적으로는 Code Rabbit 은 정적 분석을 하면서 생길 수 있는 문제를 지적을 해주는 것이였고,

Spring boot 가 없어도 돌아갈 수 있는 100% 동작을 보장을 하는 명시적이록 안전한 방법을 설명을 해준것이다.

 

해당 프로젝트는 처음부터 끝까지 Spring boot 환경에서 사용을 할 것이 확실하기 때문에 @JsonCreator 와 @JsonProperty 을

사용하지 않기로 하였다.

사용하여 안전한 코드를 만드는 것은 중요하지만, Spring boot 에서 지원을 해주고 있고,

눈에 익숙하고 이해가 빠른 코드가 좋은 코드라고 생각을 하였기 때문이다.

 

하지만 어느 환경에서든 확실하게 돌아가길 원한다면 JsonCreator 와 JsonProperty를 사용하는 것이 좋을 것이다.

 

 

 

마무리 & 느낀점

이번에 Gemini 2.5 pro 를 무료로 사용을 해보게 되었는데, 지금 사용하면서 할루시네이션을 보지 못하였다.

 

AI 답변은 전부 다시 검색을 하거나, 다른 AI 에게 검증을 하였다.

그리고 Gemini DeepSearch 로 다시 검색을 하면서 출처를 찾아 내부 내용을 다시 찾는 등 할루시네이션이 있나 걱정을 하였지만,

 

URL 이 잘못되어 나오지 않는 문제를 제외하고 전부 정확하게 나왔다.

 

인공지능으로 공부를 하면서 빠르게 나에게 맞는 정보를 빠르게 검색하는 부분에서 좋았다.

코딩을 위한 것으로 사용하는 것이 아닌 검색용, 공부용으로 사용하면 검색하는 시간이 줄어서 좋은 것 같다.

 

 

 

 

 

추가적인 정보

https://docs.google.com/document/d/e/2PACX-1vQvUkv4z8lSVpOZgecROaaKFsnlHVIiXJqDYoeda8Wy5TLA4gbixuu4aT8T2uTsa1U5HL8US6H1pRHV/pub

 

Spring Boot -parameters 옵션 검증

Spring Boot 빌드 자동화에서의 Java 컴파일러 -parameters 인수에 대한 심층 분석 보고서 I. 요약: -parameters 규칙의 명확한 해결 본 보고서는 Spring Boot와 Gradle 빌드 시스템의 상호작용, 특히 Java 컴파일러

docs.google.com

 

 

https://docs.google.com/document/d/e/2PACX-1vQpiVjCvFLSaOVU98EEkhSBzsIAaGjalw2an0JfJAXWPqUiwLvHzAeVKJosm5M_ytO4rMT97rkI2Mr4/pub

 

Spring Boot의 이중 파라미터 이름 해결

설정보다 관례의 실제: Jackson 파라미터 이름 확인을 위한 스프링 부트의 이중 메커니즘 솔루션에 대한 최종 증명 섹션 1: "이름 없는" 파라미터의 과제: 역사적 맥락 jackson-module-parameter-names 모듈

docs.google.com

 

링크를 찾는데 사용했지만 좋은 내용들이 많아서 공유합니다.