자바 백엔드 입문 8편 — 자바 Optional null 안전

2026-05-17자바 백엔드 입문

자바 백엔드 입문 8편. NullPointerException을 막는 모던 자바 표준 자바 Optional의 표준 사용법을 봉투 비유로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 8편 — 자바 Optional null 안전

이 글은 자바 백엔드 입문 시리즈 59편 중 8편이에요. 이번 8편은 자바 8(2014)에 도입돼 모던 자바의 표준이 된 자바 Optional — null 안전 처리.

NullPointerException — 자바의 영원한 적

자바 개발자가 가장 자주 만나는 에러 = NullPointerException(NPE). 1965년 "십억 달러 실수" 라고 불리는 null 참조 — 영국 컴퓨터 과학자 Tony Hoare 가 ALGOL W에 null 도입한 걸 평생 후회한다고 말한 일화는 유명.

User user = userRepository.findByEmail("alice@example.com");
String name = user.getName();   // user가 null이면 → NullPointerException 폭발

이 시리즈 4편 어노테이션 까지 다룬 자바 코드 어디서나 null 검사를 안 하면 NPE 위험. 자바 8 Optional 도입의 동기.

Optional 비유 — 봉투 같은 객체

Optional = "값이 들어 있을 수도 있고 비어 있을 수도 있는 봉투". 봉투를 받은 사람은 — 봉투를 열기 전에 "안에 뭔가 있나?" 확인 후 꺼내야 함.

Optional<User> userOpt = userRepository.findByEmail("alice@example.com");

if (userOpt.isPresent()) {
    User user = userOpt.get();             // 안전하게 꺼냄
    System.out.println(user.getName());
} else {
    System.out.println("사용자 없음");
}

null 가능성을 타입 시스템에 명시Optional<User> 타입이 "이 값은 없을 수도 있다" 를 말함. 호출자가 null 검사를 "잊을 수 없게" 강제.

Optional 생성

Optional<String> empty = Optional.empty();              // 비어 있음
Optional<String> nonEmpty = Optional.of("Hello");       // 비어 있을 수 없음
Optional<String> nullable = Optional.ofNullable(maybeNull);   // null 허용

Optional.of(null) 은 NullPointerException 던짐"null이 들어올 수 있는 곳" 엔 무조건 ofNullable. 헷갈리기 쉬운 함정.

값 꺼내기 — 5가지 표준 패턴

(1) isPresent() + get() — 옛 스타일 (지양)

if (userOpt.isPresent()) {
    User user = userOpt.get();
    process(user);
}

작동은 하지만 "if-else" 가 도로 살아남. Optional 도입 의도 무색. 모던 자바는 다음 패턴들 사용.

(2) ifPresent(Consumer) — 값 있으면 실행

userOpt.ifPresent(user -> process(user));
userOpt.ifPresent(this::process);

값이 있을 때만 실행. 람다 (9편 람다·Stream 에서 깊이).

(3) orElse(default) — 없으면 기본값

User user = userOpt.orElse(User.GUEST);
String name = userRepository.findByEmail(email)
    .map(User::getName)
    .orElse("Anonymous");

값이 없으면 매개변수의 기본값 반환. 가장 자주 쓰는 패턴.

(4) orElseGet(Supplier) — 없으면 함수 실행

User user = userOpt.orElseGet(() -> createDefaultUser());

orElse 와 비슷한데 — 기본값이 "비싼 객체 생성" 일 때 orElseGet 사용 (값 있으면 생성 안 함, 지연 평가).

(5) orElseThrow(Supplier) — 없으면 예외

User user = userOpt.orElseThrow(() -> new UserNotFoundException(email));

비즈니스 로직에서 "없으면 에러" 가 명확할 때. Spring 백엔드 거의 표준.

map·filter — 변환·필터

Optional은 "한 개 짜리 Stream" 처럼 다룰 수 있어요.

map — 변환

String userName = userRepository.findByEmail(email)
    .map(User::getName)              // User → String
    .orElse("Anonymous");

값이 있으면 함수 적용 → 새 Optional. 없으면 빈 Optional 그대로 통과.

filter — 조건 필터

Optional<User> active = userRepository.findByEmail(email)
    .filter(User::isActive);          // active만 통과

조건 안 맞으면 빈 Optional.

flatMap — 중첩 Optional 평탄화

Optional<Order> latestOrder = userRepository.findByEmail(email)
    .flatMap(User::findLatestOrder); // User → Optional<Order>

map 결과가 또 Optional이면 — flatMap 으로 평탄화. Optional<Optional<Order>> 같은 중첩 회피.

체이닝 — 모던 자바의 진짜 매력

여러 패턴을 한 줄로 묶으면 — 함수형 표현이 완성.

String greeting = userRepository.findByEmail(email)        // Optional<User>
    .filter(User::isActive)                                 // active만
    .map(User::getName)                                     // → String
    .map(name -> "안녕하세요, " + name)                       // → String
    .orElse("게스트님 환영합니다");                            // 없으면 기본값

7~8줄 if-else가 한 표현식으로. 한국 회사 백엔드 코드의 표준 풍경.

Optional 안티패턴 — 절대 하지 말 것

(1) 필드·매개변수에 Optional 사용

public class User {
    private Optional<String> nickname;    // ❌ 절대 X
}

public void process(Optional<User> user) { ... }   // ❌ 매개변수도 X

Optional은 메서드 반환값 전용. 필드·매개변수에 박는 건 자바 설계자(Brian Goetz) 가 명시적으로 "의도와 다르다" 라고 말함.

(2) isPresent() + get() 박았는데 변환 없을 때

// ❌ 안티패턴
if (userOpt.isPresent()) {
    User user = userOpt.get();
    process(user);
}

// ✅ 모던 스타일
userOpt.ifPresent(this::process);

(3) Optional.of(null) 호출 가능 코드

// ❌ NullPointerException 폭발
return Optional.of(maybeNullValue);

// ✅ 안전
return Optional.ofNullable(maybeNullValue);

(4) orElse 에 비싼 객체 생성

// ❌ 매번 비싼 객체 생성 (값 있어도 생성됨)
User user = userOpt.orElse(loadDefaultUserFromDB());

// ✅ 지연 평가 — 필요할 때만 생성
User user = userOpt.orElseGet(() -> loadDefaultUserFromDB());

(5) 컬렉션을 Optional로

// ❌ 빈 리스트 vs 비어 있음을 둘 다 표현해 혼란
public Optional<List<Order>> findOrders(Long userId) { ... }

// ✅ 빈 리스트 반환이 표준
public List<Order> findOrders(Long userId) { ... }   // 없으면 emptyList

컬렉션은 Collections.emptyList() 가 충분히 "비어 있음" 을 표현해줘서 — Optional로 감쌀 필요 X.

Spring·JPA에서 자주

이 시리즈 45편 Repository 에서 깊이 — JPA가 표준으로 Optional 반환.

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

// 서비스
public User getActiveUser(String email) {
    return userRepository.findByEmail(email)
            .filter(User::isActive)
            .orElseThrow(() -> new UserNotFoundException(email));
}

JPA Repository 메서드가 Optional<T> 반환하는 게 한국 회사 표준. null 반환 메서드는 옛 스타일.

💡 입문자 빠른 룰

메서드 반환 타입이 Optional<T> 면 — .map(...).orElse(...) 또는 .orElseThrow(...) 한 줄로 처리. isPresent + get 옛 스타일은 손에서 나오는 순간 다시 생각.

한 줄 정리 — 자바 Optional = NPE 방어 봉투. 메서드 반환값 전용. map·filter·orElse·orElseThrow 체이닝이 모던 자바 표준. 필드·매개변수·컬렉션엔 박지 말 것.

시험 직전 한 번 더 — 자바 Optional 입문자가 매번 헷갈리는 것

  • Optional = 자바 8+ null 안전 봉투 객체
  • 동기 = NullPointerException 회피, 타입 시스템에 null 가능성 명시
  • 생성 = Optional.empty()·Optional.of(value)·Optional.ofNullable(maybeNull)
  • Optional.of(null) = NPE 던짐 (가장 자주 틀리는 함정)
  • 값 있는지 = isPresent()·isEmpty()(자바 11+)
  • ifPresent(c) = 값 있으면 실행 (옛 스타일 isPresent + get 대체)
  • orElse(default) = 없으면 기본값 (즉시 평가)
  • orElseGet(supplier) = 없으면 함수 실행 (지연 평가)
  • orElseThrow(supplier) = 없으면 예외 (한국 회사 백엔드 표준)
  • map(fn) = 값 있으면 변환
  • filter(pred) = 조건 안 맞으면 빈 Optional
  • flatMap(fn) = 중첩 Optional 평탄화
  • 안티패턴 1 = 필드·매개변수에 Optional 사용 (절대 X)
  • 안티패턴 2 = isPresent + get 패턴 (ifPresent로 대체)
  • 안티패턴 3 = Optional.of(null)
  • 안티패턴 4 = orElse(비싼객체()) (대신 orElseGet)
  • 안티패턴 5 = 컬렉션을 Optional로 감싸기 (빈 리스트로 충분)
  • JPA Repository = Optional<T> findById(id) 표준
  • null 반환 메서드 = 옛 스타일, Optional 반환이 모던
  • 자바 백엔드 = 매일 Optional 다룸

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!