개발/java,spring

Checked Exception vs Unchecked Exception 차이: 실무에서 어떻게 선택해야 할까?

Mr.Lee 하루 2026. 3. 31. 16:58

Checked Exception vs Unchecked Exception 차이: 실무에서 어떻게 선택해야 할까?

Java를 어느 정도 공부하다 보면 반드시 마주치는 개념이 있습니다.
바로 Checked Exception vs Unchecked Exception입니다.

처음에는 단순히

“Checked는 try-catch 해야 하고, Unchecked는 안 해도 된다”
이 정도로 이해하고 넘어가기 쉽습니다.

하지만 실무에서는 이 선택 하나로
코드 구조, 유지보수성, 예외 설계 방식까지 완전히 달라집니다.

이번 글에서는 단순 개념 정리를 넘어서
언제 Checked를 쓰고, 언제 Unchecked를 써야 하는지까지
실무 기준으로 정리해보겠습니다.


Checked Exception vs Unchecked Exception 한눈에 보기

먼저 핵심 차이를 빠르게 정리해보겠습니다.

구분 Checked Exception Unchecked Exception
상속 Exception RuntimeException
컴파일 체크 강제됨 강제되지 않음
처리 방식 try-catch 또는 throws 필수 선택
용도 복구 가능한 예외 개발 실수 / 비즈니스 오류
대표 예외 IOException, SQLException NullPointerException, IllegalArgumentException

이 표만 보면 간단해 보이지만, 실제로는 훨씬 중요한 의미를 가지고 있습니다.


Java Exception 구조에서의 위치

Java Exception 구조에서 두 개념은 이렇게 나뉩니다.

Throwable
└── Exception
    ├── RuntimeException (Unchecked)
    └── Checked Exception

즉,

  • RuntimeException을 상속하면 → Unchecked Exception
  • 그 외 Exception을 상속하면 → Checked Exception

이 구조를 이해하면 왜 동작이 다른지 자연스럽게 연결됩니다.


Checked Exception이란?

Checked Exception은 컴파일 단계에서 반드시 처리 여부를 확인받는 예외입니다.

즉, 아래 둘 중 하나를 반드시 해야 합니다.

  • try-catch로 처리
  • throws로 위임

예시

public void readFile() {
    FileReader reader = new FileReader("test.txt"); // 컴파일 에러 발생
}

위 코드는 컴파일되지 않습니다.
왜냐하면 FileNotFoundException이 Checked Exception이기 때문입니다.

해결 방법 1: try-catch

try {
    FileReader reader = new FileReader("test.txt");
} catch (FileNotFoundException e) {
    System.out.println("파일이 없습니다.");
}

해결 방법 2: throws

public void readFile() throws FileNotFoundException {
    FileReader reader = new FileReader("test.txt");
}

Checked Exception의 의도

Checked Exception은 단순한 제약이 아니라
“이건 반드시 신경 써야 하는 실패 상황이다”라는 의미입니다.

대표적인 특징:

  • 외부 자원 접근
  • 실패 가능성이 높음
  • 복구 가능성이 있음

예:

  • 파일 읽기 실패
  • DB 연결 실패
  • 네트워크 요청 실패

즉, Checked Exception은
👉 “이건 무시하면 안 된다”는 신호입니다.


Unchecked Exception이란?

Unchecked Exception은 RuntimeException을 상속한 예외입니다.

이 예외는 컴파일러가 처리 여부를 강제하지 않습니다.

예시

String text = null;
System.out.println(text.length()); // NullPointerException
int number = Integer.parseInt("abc"); // NumberFormatException

이 코드는 컴파일은 되지만 실행 중 예외가 발생합니다.


Unchecked Exception의 의도

Unchecked Exception은 대부분 이런 상황을 의미합니다.

  • 개발자의 실수
  • 잘못된 입력
  • 잘못된 상태

즉,

👉 코드를 고쳐야 하는 문제

대표 예:

  • NullPointerException → null 체크 안 함
  • IllegalArgumentException → 잘못된 값 전달
  • IllegalStateException → 잘못된 상태에서 호출

핵심 차이: “복구 가능 vs 코드 오류”

이 둘의 가장 중요한 차이는 여기입니다.

Checked Exception

👉 복구 가능성이 있는 예외

  • 재시도 가능
  • 대체 로직 가능
  • 사용자에게 안내 가능

Unchecked Exception

👉 코드 자체가 잘못된 상태

  • 로직 수정 필요
  • 방어 코드 필요
  • 설계 문제일 가능성 높음

실무에서 가장 중요한 판단 기준

실무에서는 이렇게 생각하면 거의 틀리지 않습니다.

1. 외부 시스템 문제인가?

  • 파일, DB, 네트워크 → Checked 고려

2. 개발자 실수인가?

  • null, 잘못된 값 → Unchecked

3. 비즈니스 규칙 위반인가?

  • 주문 수량 음수, 로그인 불가 상태 → Unchecked

예시로 보는 차이

예시 1: 파일 읽기 (Checked)

public String readFile(String path) throws IOException {
    return Files.readString(Path.of(path));
}

이건 실패할 수 있는 상황이 자연스럽습니다.
그래서 Checked Exception이 적절합니다.


예시 2: 잘못된 값 (Unchecked)

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("나이는 0 이상이어야 합니다.");
    }
}

이건 호출하는 쪽이 잘못 사용한 것입니다.
그래서 Unchecked가 맞습니다.


예시 3: 비즈니스 예외 (Unchecked)

public void order(int stock, int quantity) {
    if (quantity > stock) {
        throw new OutOfStockException("재고 부족");
    }
}

이건 시스템 오류가 아니라 비즈니스 규칙 위반입니다.

그래서 대부분 RuntimeException 기반으로 만듭니다.


왜 요즘은 Unchecked Exception을 더 많이 쓸까?

이건 실무에서 정말 중요한 포인트입니다.

이유 1: 코드가 너무 지저분해짐

Checked Exception을 남발하면 이런 코드가 됩니다.

public void service() throws IOException, SQLException, ParseException {
    // ...
}

호출하는 쪽도 계속 throws를 추가해야 합니다.


이유 2: 예외 전파가 과도해짐

Controller까지 계속 전달되면서
코드가 “예외 전달용 코드”가 되어버립니다.


이유 3: 실제로는 복구 안 하는 경우가 많음

현실적으로는 대부분 이렇게 합니다.

catch (Exception e) {
    log.error(e);
    throw new RuntimeException(e);
}

즉, Checked로 시작해도 결국 RuntimeException으로 감쌉니다.


그래서 실무에서는 이렇게 정리합니다

✔ Checked Exception 사용 기준

  • 외부 리소스 접근
  • 반드시 호출자가 처리해야 하는 경우

예:

  • 파일
  • DB
  • 네트워크

✔ Unchecked Exception 사용 기준

  • 잘못된 요청
  • 잘못된 상태
  • 비즈니스 로직 위반

예:

  • 회원 없음
  • 재고 부족
  • 권한 없음

Custom Exception 설계 기준

실무에서는 직접 예외를 만드는 경우가 많습니다.

RuntimeException 기반 (추천)

public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

이 방식이 가장 많이 사용됩니다.


Checked Exception 기반 (특수 상황)

public class ExternalApiException extends Exception {
    public ExternalApiException(String message) {
        super(message);
    }
}

외부 시스템 연동처럼
“반드시 처리해야 하는 경우”에 사용합니다.


잘못된 사용 사례

❌ 무조건 Checked Exception

public void validate(int value) throws Exception {
    if (value < 0) {
        throw new Exception("잘못된 값");
    }
}

👉 의미 없음 + 코드만 복잡해짐


❌ 모든 예외 RuntimeException

try {
    // DB 처리
} catch (Exception e) {
    throw new RuntimeException(e);
}

👉 예외 의미가 사라짐


추천 설계 패턴

실무에서 가장 많이 쓰는 구조입니다.

Repository

  • Checked 또는 라이브러리 예외 발생

Service

  • 비즈니스 예외로 변환 (RuntimeException)

Controller

  • 전역 예외 처리

예시

public User getUser(Long id) {
    return userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException("사용자 없음"));
}

핵심 요약

이 주제에서 반드시 기억해야 할 핵심입니다.

1. Checked Exception

  • 컴파일러 강제
  • 복구 가능한 상황
  • 외부 리소스 중심

2. Unchecked Exception

  • 강제 없음
  • 코드 문제 / 비즈니스 문제
  • RuntimeException 기반

3. 실무 기준

👉 대부분 RuntimeException 사용
👉 Checked는 제한적으로 사용


결론: 중요한 건 “문법”이 아니라 “설계”

Checked vs Unchecked는 단순 문법 차이가 아닙니다.

이 선택은 곧 다음을 의미합니다.

  • 이 예외를 누가 책임질 것인가
  • 어디서 처리할 것인가
  • 복구 가능한가, 아닌가

결국 핵심은 이 한 줄입니다.

👉 “이 예외는 처리해야 하는 문제인가, 고쳐야 하는 문제인가?”

이 기준만 잡으면 설계가 훨씬 명확해집니다.