문제 상황
Spring Boot + JPA 환경에서 엔티티를 작성하던 중, 연관관계 컬렉션(List)에 데이터를 추가하려고 할 때 NullPointerException이 발생했다.
에러 메시지:
Cannot invoke "java.util.List.add(Object)"
because the return value of "goorm.ddok.study.domain.StudyRecruitment.getTraits()" is null
엔티티에서는 이미 컬렉션을 new ArrayList<>()로 초기화해둔 상태였는데도, 서비스 로직에서 .add() 호출 시 NPE가 발생했다.
원인
문제의 핵심은 Lombok의 @Builder 동작 방식이다.
@Builder는 필드 초기화식이 아닌, 생성자를 통해 객체를 생성한다.
따라서 다음과 같은 코드가 있을 때:
@OneToMany(mappedBy = "studyRecruitment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<StudyRecruitmentTrait> traits = new ArrayList<>();
- new StudyRecruitment() → traits는 new ArrayList<>()로 정상 초기화
- StudyRecruitment.builder().build() → traits는 null로 세팅됨
즉, 빌더로 엔티티를 생성하면 초기화식이 무시되면서 컬렉션이 null이 되어버린다.
실패 코드
@Entity
@Builder(toBuilder = true)
public class StudyRecruitment {
@OneToMany(mappedBy = "studyRecruitment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<StudyRecruitmentTrait> traits = new ArrayList<>();
@OneToMany(mappedBy = "studyRecruitment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<StudyApplication> applications = new ArrayList<>();
@OneToMany(mappedBy = "studyRecruitment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<StudyParticipant> participants = new ArrayList<>();
}
→ 빌더로 생성하면 traits, applications, participants가 전부 null
해결 방법
Lombok에서 필드 초기화식을 유지하려면 @Builder.Default를 명시해야 한다.
@Entity
@Builder(toBuilder = true)
public class StudyRecruitment {
@OneToMany(mappedBy = "studyRecruitment", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyRecruitmentTrait> traits = new ArrayList<>();
@OneToMany(mappedBy = "studyRecruitment", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyApplication> applications = new ArrayList<>();
@OneToMany(mappedBy = "studyRecruitment", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyParticipant> participants = new ArrayList<>();
}
이제 StudyRecruitment.builder().build()를 호출해도 컬렉션이 안전하게 초기화된다.
마무리
- Lombok @Builder는 필드 초기화식을 무시한다.
→ 빌더를 사용할 때는 직접 값을 지정하지 않으면 null이 들어갈 수 있다. - 연관관계 컬렉션은 항상 null-safe 해야 한다.
→ JPA 엔티티에서 컬렉션이 null이면 .add(), .size() 호출 시 NPE로 이어진다. - 컬렉션 필드에는 반드시 @Builder.Default를 사용하거나 생성자/팩토리에서 초기화하라.
→ 빌더를 쓰든 생성자를 쓰든, 일관되게 안전한 초기화 전략을 가져가는 게 중요하다. - “엔티티 생성 시 기본 상태를 보장하는 것”은 도메인 객체의 책임이다.
→ 서비스 계층에서 null 체크로 땜질하지 말고, 엔티티 자체가 항상 일관된 상태를 유지하도록 설계해야 한다.