우대사항: Kotlin 기반 서비스 개발 경험
IT 기업들이 최근 요구하는 기술 스택이 무엇인지 인지하기 위해 채용 공고를 자주 살펴보는 편이다.
최근 백엔드 개발자 채용 공고, 특히 빅테크 기업이나 빠르게 성장하는 스타트업의 자격 요건을 보면 눈에 띄는 공통점이 있다. Java 기반의 Spring Boot가 여전히 주류이지만, 우대 사항이나 자격 요건에 Kotlin + Spring Boot 환경 개발 경험이 매우 자주 등장한다는 점이다.
단순히 새로운 언어가 유행하기 때문일까, 아니면 확실한 기술적 이점이 있기 때문일까? Java와 Kotlin의 코드를 직접 비교해보며 왜 기업들이 점점 Kotlin을 선호하는지 알아보았다.
1. 생산성의 차이 [Data Class]
가장 먼저 체감되는 부분은 코드의 간결함이다. Java로 개발할 때 DTO 하나를 만들기 위해 꽤 많은 반복 노동이 필요했다. Lombok이라는 훌륭한 라이브러리가 존재하지만, 언어 차원에서 이를 지원하는 것과는 근본적인 차이가 있다.
Java (Lombok)
@Getter
@ToString
@EqualsAndHashCode
@AllArgsConstructor
public class UserDto {
private String name;
private int age;
private String email;
}
Kotlin
data class UserDto(
val name: String,
val age: Int,
val email: String
)
Kotlin의 data class는 생성자, Getter, equals(), hashCode(), toString()을 컴파일 시점에 자동으로 생성한다. 코드가 짧아진다는 것은 단순히 타자 칠 일이 줄어드는 것을 넘어, **"읽어야 할 코드가 줄어들어 비즈니스 로직에 더 집중할 수 있다"**는 큰 장점이 된다.
2. Null Safety
Java 개발자라면 누구나 한 번쯤 NullPointerException(NPE) 때문에 로그를 뒤져본 경험이 있을 것이다. Java에서는 컴파일 시점에 변수에 null이 할당될지 여부를 확신할 수 없어 방어 로직이 비대해지거나 런타임 에러가 발생하곤 한다.
Kotlin은 이 문제를 컴파일 단계에서 원천 차단한다.
Java
public void printLength(String str) {
// 실수로 null 체크를 누락했다면 NPE 발생 위험이 있다
System.out.println(str.length());
}
Kotlin
// String? 은 null이 허용됨을 명시한다
fun printLength(str: String?) {
// 컴파일 에러 발생! null 가능성이 있는 변수는 직접 접근할 수 없다
// println(str.length)
// 안전한 호출 (?.)을 사용하여 null 체크와 실행을 동시에 처리한다
println(str?.length)
}
3. 비동기 처리 [Coroutines]
최근 대용량 트래픽 처리를 위해 비동기 논블로킹 방식인 Spring WebFlux를 도입하는 사례가 늘고 있다. 하지만 Java의 Reactor 패턴(Mono, Flux)은 러닝커브가 가파르고 가독성이 상대적으로 떨어진다.
Java (Reactor)
public Mono<Delivery> getDelivery(String userId) {
return userRepository.findById(userId)
.flatMap(user -> deliveryRepository.findByUserNo(user.getUserNo()))
.subscribeOn(Schedulers.boundedElastic());
}
Kotlin (Coroutines)
suspend fun getDelivery(userId: String): Delivery {
val user = userRepository.findById(userId) ?: throw UserNotFoundException()
return deliveryRepository.findByUserNo(user.userNo)
}
Kotlin의 Coroutine을 사용하면 suspend 키워드를 통해 비동기 코드를 마치 동기 코드처럼 작성할 수 있다. 가독성이 압도적으로 좋아지며 디버깅 또한 용이하다.
4. 마이그레이션 실습
최근 진행한 토이 프로젝트의 코드를 직접 마이그레이션 해보았다. Github API를 통해 커밋 내역을 가져와 DTO로 변환하는 로직이다.
Java (Before)
ArrayList를 생성하고 for 문을 돌며 데이터를 추가한다. Getter 호출이 중첩되어 코드가 길어진다.
public List<CommitResponse> getCommits(String token, String repoName) throws IOException {
GitHub github = new GitHubBuilder().withOAuthToken(token).build();
GHRepository repo = github.getRepository(repoName);
List<CommitResponse> commits = new ArrayList<>();
for (GHCommit commit : repo.queryCommits().pageSize(20).list()) {
commits.add(new CommitResponse(
commit.getSHA1(),
commit.getCommitShortInfo().getMessage(),
commit.getCommitShortInfo().getAuthor().getName(),
commit.getCommitDate().toString()
));
}
return commits;
}
Kotlin (After)
Kotlin의 컬렉션 함수인 map을 활용했다. new 키워드가 사라졌고 프로퍼티 접근 문법을 통해 가독성이 향상되었다. 선언형 스타일 덕분에 데이터 흐름이 한눈에 파악된다.
fun getCommits(token: String, repoName: String): List<CommitResponse> {
val github = GitHubBuilder().withOAuthToken(token).build()
val repo = github.getRepository(repoName)
return repo.queryCommits().pageSize(20).list()
.map { commit ->
CommitResponse(
sha = commit.sha1,
message = commit.commitShortInfo.message,
author = commit.commitShortInfo.author.name,
date = commit.commitDate.toString()
)
}
}
5. Java와의 100% 상호 운용성
이러한 장점에도 불구하고 기존 Java 코드를 모두 교체해야 했다면 도입이 쉽지 않았을 것이다. 그러나 Kotlin은 Java와 100% 호환된다.
- 하나의 프로젝트 내에서
.java와.kt파일이 공존 가능하다. - 기존의 방대한 Java 라이브러리를 그대로 사용할 수 있다.
- 서비스 운영 중에도 점진적인 마이그레이션이 가능하다.
마무리하며
이전의 나에게 Kotlin은 Android 개발 전용 언어였다. 그러나 직접 Java와 비교해보니 백엔드 개발자들이 왜 Kotlin에 열광하는지 명확히 알 수 있었다.
Java 역시 꾸준히 발전하고 있으나, Spring 프레임워크가 Kotlin을 First-class 언어로 공식 지원하는 만큼 백엔드 개발자에게 Kotlin은 이제 필수 역량으로 자리 잡고 있다.