Java + Kotlin 혼재 프로젝트에서 policy 도메인을 코틀린으로 마이그레이션하던 중, Policy.java(@Entity)를 Policy.kt로 전환하면서 예상치 못한 문제들을 마주쳤습니다.
Policy.java → Policy.kt 전환 후 빌드하자 다음 오류가 발생했습니다.
cannot find symbol: class QPolicy
원인: QueryDSL은 @Entity 클래스를 감지해 Q클래스를 자동 생성합니다. 이 과정은 Java 전용 도구인 APT(Annotation Processing Tool) 가 담당합니다. APT는 .java 파일만 처리하기 때문에 Policy.kt로 전환하자 QPolicy가 생성되지 않았습니다.
Policy.java → APT → QPolicy.java 생성 ✅
Policy.kt → APT 인식 불가 → QPolicy 미생성 ❌
코틀린 파일도 APT가 처리할 수 있도록 kapt(Kotlin Annotation Processing Tool) 를 도입했습니다. kapt는 .kt 파일을 Java 스텁으로 변환한 뒤 APT에 넘겨주는 번역기 역할을 합니다.
Policy.kt → kapt(Java 스텁 변환) → APT → QPolicy 생성 ✅
build.gradle.kts에 kapt를 추가했고, 실제로 QPolicy는 생성됐습니다.
kapt를 추가하자 policy와 전혀 무관한 파일들에서 연쇄적으로 오류가 발생했습니다.
variable memberRepository not initialized in the default constructor
cannot find symbol: variable log
constructor EstateRegionDto cannot be applied to given types
근본 원인: kapt는 QPolicy 생성을 위해 Policy.kt만 처리하는 게 아니라, 프로젝트의 모든 파일을 스텁으로 변환합니다. 이 과정에서 Lombok이 아직 처리되지 않은 상태로 Java 파일들의 스텁이 만들어지면서 오류가 발생했습니다.
kapt 스텁 생성 단계 (Lombok 처리 전):
ActorProvider.java
@RequiredArgsConstructor → 생성자 미생성 → private final 필드 초기화 불가 💥
EstateRegionCache.java
@Slf4j → log 변수 미생성 💥
EstateRegionDto.java
@AllArgsConstructor → 전체 인자 생성자 미생성 💥
kapt/KSP 모두 스텁 생성 단계가 Lombok 처리보다 먼저 실행되는 구조적 한계가 있습니다. 이는 도구의 문제가 아니라 Java + Kotlin + Lombok이 혼재하는 환경의 컴파일 순서 문제입니다.
kapt 없을 때:
Java → annotationProcessor(Lombok 처리) → 컴파일 ✅
kapt 추가 후:
전체 파일 → kapt 스텁 생성 (Lombok 미처리) → 💥
결국 Policy.java를 그대로 유지하는 방향으로 결정했습니다.
| 방법 | 결과 |
|---|---|| Policy.kt + kapt | QPolicy 생성되지만 프로젝트 전체 Lombok 파일 오류 💥 |
| Policy.java 유지 | QPolicy 정상 생성, Lombok 문제 없음 ✅ |
핵심 교훈: 코틀린은 Java와 마찰 없이 혼재할 수 있지만, QueryDSL의 Q클래스 생성이 필요한 @Entity 클래스는 Java로 유지하는 것이 현실적입니다. 비즈니스 로직이 담긴 Service, Repository, DTO 등은 코틀린으로 자유롭게 전환할 수 있으며, 이것만으로도 코틀린의 장점을 충분히 활용할 수 있습니다.