TECH/웹개발

[Java] Optional 완벽 가이드: NPE(NullPointerException)와 이별하는 깔끔한 방법

런코리치 2026. 3. 7. 08:00

지긋지긋한 if (obj != null) 코드, 이제 그만 쓰고 싶으시죠? 자바 8에서 도입된 Optional은 Null을 우아하게 처리하기 위해 등장했지만, 잘못 쓰면 코드가 더 지저분해지고 성능까지 깎아먹습니다. 오늘은 'Optional답게' 코드를 짜는 법과, 시니어 개발자들이 질색하는 안티 패턴들을 정리해 드립니다.


1. Optional을 사용하는 진짜 이유

많은 분이 "NPE를 방지하기 위해서"라고 답하지만, 더 정확한 목적은 "메서드의 반환 값이 '없을 수도 있음'을 명시적으로 표현하기 위함"입니다.

즉, 반환 타입이 Optional<User>라면, 이 메서드를 사용하는 개발자는 "아, 이 결과는 null일 수 있으니 반드시 대처해야겠구나"라는 것을 코드를 보자마자 알 수 있습니다.


2. 제발 이렇게 쓰지 마세요: 최악의 안티 패턴 3가지

❌ 안티 패턴 1: 무분별한 .get() 사용

Optional을 사용하면서 내부 값을 꺼내기 위해 바로 .get()을 호출하는 것은 가장 흔한 실수입니다.

Java
// 최악의 예시
Optional<User> user = userRepository.findById(id);
User result = user.get(); // 만약 값이 없다면 NoSuchElementException 발생!

이 방식은 기존의 null 체크를 하지 않는 것과 다를 바가 없습니다. Optional을 쓰는 의미가 완전히 사라집니다.

❌ 안티 패턴 2: ifPresent()를 기존 if문처럼 사용하기

Java
// 기존 방식
if (user != null) {
    System.out.println(user.getName());
}

// Optional을 잘못 활용한 예시
userOptional.ifPresent(u -> {
    System.out.println(u.getName());
});

단순히 if문을 ifPresent로 바꾸는 것은 코드만 길어질 뿐 큰 이점이 없습니다.

❌ 안티 패턴 3: Optional을 필드(Field)나 파라미터로 사용

Optional은 직렬화(Serialization)를 지원하지 않습니다. 따라서 엔티티의 필드나 메서드의 인자로 사용하는 것은 설계상 매우 위험합니다.

  • 권장: 반환 타입(Return Type)으로만 사용하세요.

3. 실무에서 바로 쓰는 Optional 정석 패턴

이제 실무에서 "코드 좀 짤 줄 아네?"라는 소리를 듣는 깔끔한 패턴들을 살펴보겠습니다.

✅ 패턴 1: orElseThrow (값이 없으면 에러 던지기)

데이터가 없을 때 예외를 발생시키는 가장 표준적인 방법입니다.

Java
User user = userRepository.findById(id)
    .orElseThrow(() -> new CustomException("해당 유저를 찾을 수 없습니다."));

✅ 패턴 2: orElse vs orElseGet (성능 차이 주의!)

이 부분은 면접에서도 자주 나옵니다.

  • orElse(new User()): 값이 있든 없든 new User()를 항상 실행합니다.
  • orElseGet(() -> new User()): 값이 없을 때만 실행합니다.
Java
// 값이 있어도 새로운 객체를 생성하는 낭비가 발생할 수 있음
User user = userRepository.findById(id).orElse(createNewUser()); 

// 값이 없을 때만 메서드가 호출됨 (성능 최적화)
User user = userRepository.findById(id).orElseGet(() -> createNewUser());

✅ 패턴 3: map과 filter 활용하기

Optional의 진가는 여기서 나옵니다. if문 없이 체이닝으로 처리하세요.

Java
String grade = userRepository.findById(id)
    .filter(u -> u.isActive())           // 활성화된 유저만 필터링
    .map(u -> u.getGrade())              // 유저 객체에서 등급 정보만 추출
    .orElse("GUEST");                    // 위 과정에서 하나라도 없으면 "GUEST" 반환

4. Optional 사용 시 반드시 기억할 5계명

  1. 단순히 null 체크가 목적이라면 Optional을 쓰지 마세요. (성능 오버헤드가 발생합니다.)
  2. 반환 타입으로만 사용하세요. (필드, 파라미터, 생성자 인자로 금지)
  3. Optional.ofNullable()을 활용하세요. (null일 수도 있는 값을 감쌀 때 안전합니다.)
  4. 컬렉션(List, Map 등)은 Optional로 감싸지 마세요. (빈 리스트 Collections.emptyList()를 반환하는 게 정석입니다.)
  5. 원시 타입용 Optional을 따로 쓰세요. (Optional<Integer> 대신 OptionalInt, OptionalLong을 써야 박싱/언박싱 비용을 아낍니다.)

🏁 마치며: "Optional은 약속입니다"

Optional은 단순히 도구가 아니라, 동료 개발자와의 약속입니다. "이 메서드는 비어있을 수 있으니 안전하게 처리해줘"라는 메시지를 전달하는 것이죠. 오늘 배운 안티 패턴만 피해도 여러분의 코드는 훨씬 견고하고 우아해질 것입니다.

다음 포스팅에서는 자바의 또 다른 스테디셀러 주제, "String vs StringBuilder vs StringBuffer"의 내부 메모리 구조를 파헤쳐 보겠습니다!

https://run-code-rich.tistory.com/entry/java-optional-guide-anti-patterns

 

[Java] Optional 완벽 가이드: 더 이상 NullPointerException은 없다

[Java] Optional 완벽 가이드: NPE(Null)와의 전쟁을 끝내는 법자바 개발자라면 누구나 한 번쯤은 java.lang.NullPointerException 때문에 골머리를 앓아본 적이 있을 것입니다. 자바의 아버지 중 한 명인 토니

run-code-rich.tistory.com