본문 바로가기

Java

[Java] 자바의 예외 – 체크 예외 vs 언체크 예외

🧩 자바의 예외 계층 구조

 

자바의 예외(Exception)는 프로그램 실행 중 발생할 수 있는 오류를 나타내는 객체다.

자바의 모든 예외는 Throwable 클래스를 상속받으며, 이는 다시 Error Exception으로 나뉜다.

(✅: 체크 예외, ❌: 언체크 예외)

          Object
            |
         Throwable
        /         \
   Error ❌      Exception ✅
                  /       \
                 ✅    RuntimeException ❌
                              \
                               ❌
  • Error:
    • OutOfMemoryError처럼 시스템에 심각한 문제가 발생하여 애플리케이션 수준에서 복구할 수 없는 오류다.
    • 개발자는 Error를 잡으려고 해서는 안 된다.
  • Exception:
    • 애플리케이션 코드에서 발생할 수 있는 실질적인 예외의 최상위 클래스다.

 

자바는 이러한 예외를 '체크 예외(Checked Exception)'와 '언체크 예외(Unchecked Exception)'로 구분하여 처리하도록 강제한다.

이 둘의 가장 큰 차이점은 "컴파일 시점에 확인하느냐(체크) 그렇지 않으냐(언체크)"에 있다.

 


🧩 예외 처리의 기본 규칙

 

예외 처리는 폭탄 돌리기와 비슷하다. 잡아서 처리하거나, 처리할 수 없다면 밖으로 던져야 한다.

  • 예외를 잡거나 던질 때 지정된 예외 타입뿐만 아니라 그 자식들도 함께 처리된다.
  • 예를 들어, Exception을 catch로 잡으면, Exception의 자식 예외는 모두 잡을 수 있다.
  • 또한, Exception을 throws로 던지면, Exception의 자식 예외는 모두 던질 수 있다.

만약 예외를 처리하지 못하고 계속 던지게 되면, 자바의 main 쓰레드의 경우 예외 로그를 남기면서 시스템이 종료된다.

 

🚨 하지만 웹 애플리케이션의 경우 여러 사용자의 요청을 처리하기 때문에 하나의 예외 때문에 전체 시스템이 종료되면 안 된다.

따라서 해당 예외를 받아서 오류 페이지를 보여주거나, API를 통해 오류 응답을 보내야 한다.

 


✅ 체크 예외 (Checked Exception)

 

👉 체크 예외는 컴파일러가 예외 처리 여부를 강제로 확인하는 예외다. 즉, 반드시 처리해야 하는 예외다.

 

⚠️ 만약 체크 예외를 던질 수 있는 메서드를 호출하면서, 예외를 처리하는 코드를 작성하지 않으면, 컴파일 오류가 발생하게 된다.

이러한 강제성은 개발자가 프로그램 실행 중에 발생할 수 있는 예외적인 상황을 미리 예측하고 대비하도록 유도하여, 프로그램의 안정성을 높이는 데 목적이 있다.

 

특징:

  • Exception과, 그 하위 예외 중 RuntimeException을 상속하지 않는 예외
  • 컴파일 시점에 예외 처리 여부를 확인
  • try-catch를 통해 예외를 잡거나, throws를 통해 메서드 밖으로 예외를 던져서 호출한 곳에서 처리하도록 위임해야 함
  • 주로 외부 요인에 의해 발생하며, 개발자가 코드로 복구할 수 있는 상황에 사용됨

 

예시:

  • IOException: 입출력 작업 중 문제가 발생했을 때 던져지는 예외
  • FileNotFoundException: 존재하지 않는 파일에 접근하려고 할 때 발생하는 예외
  • SQLException: 데이터베이스 관련 작업 수행 중 오류가 발생했을 때 사용되는 예외
  • ClassNotFoundException: 클래스를 동적으로 로드하려고 할 때 해당 클래스를 찾을 수 없는 경우 발생하는 예외

 

처리 방법:

// 1. try-catch로 직접 처리
try {
    FileReader fr = new FileReader("file.txt");
} catch (FileNotFoundException e) {
    log.error("예외 발생: {}", e.getMessage(), e);
}

// 2. throws로 호출한 곳에 처리 위임
public void readFile() throws FileNotFoundException {
    FileReader fr = new FileReader("file.txt");
}
로그의 마지막 인자에 예외 객체를 넘기면 해당 예외의 스택 트레이스를 출력해 준다.

 


❌ 언체크 예외 (Unchecked Exception)

 

👉 언체크 예외는 컴파일러가 예외 처리 여부를 확인하지 않는 예외다. 즉, 반드시 처리할 필요는 없는 예외다.

 

따라서 언체크 예외는 try-catch나 throws를 명시적으로 사용하지 않아도 컴파일 오류가 발생하지 않는다.

 

특징:

  • RuntimeException과 그 자식 예외
  • 실행 시점(Runtime)에 발생함
  • 컴파일러가 예외 처리를 강제하지 않으므로, 개발자가 필요에 따라 선택적으로 처리할 수 있음
  • 주로 개발자의 실수나 논리적인 오류로 인해 발생

 

예시:

  • NullPointerException: null 값을 갖는 참조 변수에 접근하려고 할 때 발생
  • ArrayIndexOutOfBoundsException: 배열의 범위를 벗어난 인덱스에 접근하려고 할 때 발생
  • ArithmeticException: 0으로 나누는 등 산술적으로 불가능한 연산을 수행할 때 발생
  • ClassCastException: 잘못된 타입으로 객체를 형 변환하려고 할 때 발생
  • IllegalArgumentException: 메서드에 잘못된 인자를 전달했을 때 발생

 

처리 방법:

  • 언체크 예외 처리는 필수는 아니다.
  • 하지만 발생 가능성을 예측할 수 있고, 해당 상황에서 특별한 처리가 필요하다면 try-catch로 처리할 수 있다.
  • 근본적으로는 예외가 발생하지 않도록 코드를 수정하는 것이 더 바람직하다.
// 예외가 발생하지 않도록 미리 확인하는 것이 좋음
String text = null;
if (text != null) {
    System.out.println(text.length()); // NPE 방지
}

// 필요에 따라 try-catch로 처리할 수도 있음
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("0으로 나눌 수 없습니다.");
}

 


🧩 체크 예외의 장단점

 

체크 예외는 반드시 try-catch로 잡아서 처리하거나, 밖으로 던지는 throws를 선언해야 한다. 그렇지 않으면 컴파일 오류가 발생한다

이 때문에 다음과 같은 장점과 단점이 존재한다.

  • 장점: 개발자가 실수로 예외를 누락하지 않도록 컴파일러가 알려주므로, 시스템의 안정성이 향상된다.
  • 단점: 처리할 필요가 없거나 처리할 수 없는 예외도 모두 처리하거나 던져야 하며, 계층형 아키텍처에서 의존 관계 문제를 유발한다.
예를 들어, 리포지토리에서 특정 DB에 종속적인 체크 예외를 던진다면, 이를 호출하는 서비스 계층은 해당 기술에 종속되게 된다.

 

👉 따라서 최근 실무에서는 처리할 수 없거나, 처리할 필요 없는 예외"언체크 예외"로 설계하는 추세다.

  • 서비스 코드에서 예외 복구 로직을 직접 가지는 것보다, 전역 예외 처리(@ControllerAdvice)로 관리하는 것이 더 낫다.
  • Spring, JPA 같은 최신 프레임워크에서도 대부분 체크 예외를 "언체크 예외"로 감싸서 던진다.

 


🧩 언체크 예외의 장단점

 

언체크 예외는 예외를 잡아서 처리할 수 없을 때, 밖으로 던지는 throws를 생략할 수 있다.

이 때문에 다음과 같은 장점과 단점이 존재한다.

  • 장점: 처리할 필요가 없는 예외를 무시해도 되며, 의존 관계 문제가 없다.
  • 단점: 개발자가 실수로 예외를 누락할 수 있다.

 

👉 결국 실무 관점에서 체크 예외와 언체크 예외의 차이는 throws 선언의 필수 여부다.

 


📊 체크 예외 vs 언체크 예외 비교

구분 ✅ 체크 예외 (Checked Exception) ❌ 언체크 예외 (Unchecked Exception)
확인 시점 컴파일 시점 (Compile-time) 실행 시점 (Run-time)
처리 강제성 필수 (try-catch 또는 throws) 선택 (처리하지 않아도 컴파일은 됨)
상속 관계 Exception 클래스 상속 (단, RuntimeException 제외) RuntimeException 클래스 상속
발생 원인 외부 요인 (파일, 네트워크 등), 복구 가능성 있음 프로그래머의 실수, 논리적 오류
대표 예시 IOException, SQLException NullPointerException, ArrayIndexOutOfBoundsException
목표 안정적인 프로그램 설계 유도 개발자의 자율성에 맡김

 


📚 요약

  • 체크 예외: RuntimeException을 상속하지 않는 모든 Exception의 자식 클래스
    • 컴파일러가 예외 처리 여부를 강제로 확인(check)함
    • 따라서 개발자는 반드시 try-catch로 예외를 잡아서 처리하거나, throws 키워드를 사용하여 메서드 밖으로 예외를 던져야 함
    • 그렇지 않으면 컴파일 오류가 발생함
    • 예상 가능한 문제거나 복구 가능성 있을 때 사용
  • 언체크 예외: RuntimeException과 그 모든 자식 클래스
    • 파일러가 예외 처리 여부를 확인하지 않음
    • throws 선언 없이도 예외를 밖으로 던질 수 있으며, 잡지 않은 예외는 자동으로 호출한 쪽으로 계속 전파됨
    • 프로그래밍 오류 또는 복구 불가능할 때 사용
  • 실무에서는 체크 예외 최소화, 언체크 예외와 전역 예외 처리로 단순화