Java 8 Optional 실전 사용기

배경

회사 프로젝트가 Java 7에서 8로 마이그레이션되면서 Optional을 사용할 수 있게 되었다. 기존 코드베이스에서 NPE가 자주 발생하는 부분들을 리팩토링하면서 학습한 내용을 정리한다.

적용 사례

Before

public User findUser(Long id) {
    User user = userRepository.findById(id);
    if (user == null) {
        throw new UserNotFoundException();
    }
    return user;
}

After

public Optional<User> findUser(Long id) {
    return Optional.ofNullable(userRepository.findById(id));
}

// 사용처
User user = findUser(id)
    .orElseThrow(() -> new UserNotFoundException());

주의할 점

1. Optional을 필드로 사용하지 않기

Optional은 직렬화를 지원하지 않는다. 엔티티나 DTO의 필드로 사용하면 안 된다.

2. orElse vs orElseGet

// orElse는 항상 실행됨
user.orElse(createDefaultUser()); // 비효율

// orElseGet은 null일 때만 실행
user.orElseGet(() -> createDefaultUser()); // 권장

3. isPresent().get() 안티패턴

// Bad
if (optional.isPresent()) {
    return optional.get();
}

// Good
return optional.orElse(defaultValue);

체이닝의 편리함

기존에 null 체크로 중첩된 코드를 깔끔하게 개선할 수 있었다.

String city = Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("Unknown");

결론

Optional을 도입하면서 NPE가 30% 정도 감소했다. 다만 팀원들이 익숙해지는 데 시간이 필요했고, 기존 코드와 혼재되어 일관성을 유지하는 게 과제다. 점진적으로 핵심 도메인부터 적용해나갈 계획이다.