전체 목록
JavaMedium#58

Java의 Checked Exception과 Unchecked Exception의 차이점을 설명해주세요.

#Java#예외처리#Exception
힌트

컴파일 시점 강제 여부와 Exception 클래스 계층을 생각해보세요.

정답 및 해설

Java의 Checked Exception과 Unchecked Exception의 차이점을 설명해주세요.

Java의 예외(Exception) 체계는 크게 Checked Exception, Unchecked Exception, Error로 구분됩니다. 이 구분을 이해하면 예외를 언제 처리하고 언제 전파할지 올바른 설계 결정을 내릴 수 있습니다.

Java 예외 계층 구조

Throwable
├── Error (JVM 수준 심각한 오류)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception
    ├── IOException           ← Checked Exception
    ├── SQLException          ← Checked Exception
    ├── ClassNotFoundException ← Checked Exception
    └── RuntimeException      ← Unchecked Exception
        ├── NullPointerException
        ├── ArrayIndexOutOfBoundsException
        ├── IllegalArgumentException
        ├── ClassCastException
        └── ...

Checked Exception

Exception을 상속하지만 RuntimeException을 상속하지 않는 예외입니다. 컴파일러가 처리를 강제합니다.

특징

  • 컴파일러가 try-catch 또는 throws 선언을 강제
  • 주로 회복 가능한 외부 오류를 나타냄 (네트워크, 파일 I/O, DB 등)
  • 메서드 시그니처에 throws로 명시해야 함
// throws 선언 방식 — 호출자에게 처리 위임
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path); // IOException 발생 가능
    // ...
}

// try-catch 방식 — 직접 처리
public void readFileWithHandling(String path) {
    try {
        FileReader reader = new FileReader(path);
        BufferedReader br = new BufferedReader(reader);
        String line = br.readLine(); // IOException
        br.close();
    } catch (IOException e) {
        System.err.println("파일을 읽을 수 없습니다: " + e.getMessage());
        // 회복 로직: 기본값 사용, 재시도 등
    }
}

// try-with-resources — 자동 close (Java 7+)
public String readFileContent(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        StringBuilder content = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            content.append(line).append("\n");
        }
        return content.toString();
    }
    // IOException은 호출자에게 전파됨
}

주요 Checked Exception

예외 클래스발생 상황
IOException파일/스트림 입출력 오류
FileNotFoundException파일이 존재하지 않음
SQLExceptionDB 접근 오류
ClassNotFoundException클래스를 찾을 수 없음
InterruptedException스레드 인터럽트
ParseException파싱 오류

Unchecked Exception (RuntimeException)

RuntimeException을 상속하는 예외입니다. 컴파일러가 처리를 강제하지 않으며, 주로 프로그래밍 오류를 나타냅니다.

특징

  • try-catchthrows 없이도 컴파일 가능
  • 주로 개발자의 실수로 발생
  • 실행 시점에 발견됨
// NullPointerException — null 참조 접근
String str = null;
str.length(); // NullPointerException

// ArrayIndexOutOfBoundsException — 배열 범위 초과
int[] arr = new int[5];
arr[10] = 1; // ArrayIndexOutOfBoundsException

// ClassCastException — 잘못된 타입 캐스팅
Object obj = "hello";
Integer num = (Integer) obj; // ClassCastException

// IllegalArgumentException — 잘못된 인자 (개발자가 직접 던지는 경우)
public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("나이는 0-150 사이여야 합니다: " + age);
    }
    this.age = age;
}

// IllegalStateException — 잘못된 상태에서 메서드 호출
public void processOrder() {
    if (orderStatus != OrderStatus.PENDING) {
        throw new IllegalStateException("대기 상태의 주문만 처리할 수 있습니다.");
    }
    // 처리 로직
}

주요 Unchecked Exception

예외 클래스발생 상황
NullPointerExceptionnull 참조에 접근
ArrayIndexOutOfBoundsException배열 인덱스 초과
StringIndexOutOfBoundsException문자열 인덱스 초과
ClassCastException잘못된 타입 캐스팅
IllegalArgumentException잘못된 메서드 인자
IllegalStateException잘못된 상태에서 호출
NumberFormatException숫자 파싱 오류
StackOverflowError스택 오버플로우

Error

JVM 수준의 심각한 오류로, 일반적으로 애플리케이션에서 처리해서는 안 됩니다.

// OutOfMemoryError — 힙 메모리 부족
List<byte[]> list = new ArrayList<>();
while (true) {
    list.add(new byte[1024 * 1024]); // 결국 OutOfMemoryError

// StackOverflowError — 무한 재귀
void recurse() {
    recurse(); // StackOverflowError
}

Checked vs Unchecked 선택 기준

// Checked Exception: 호출자가 회복할 수 있는 경우
public User findUser(long userId) throws UserNotFoundException {
    User user = userRepository.findById(userId);
    if (user == null) {
        throw new UserNotFoundException("사용자를 찾을 수 없습니다: " + userId);
    }
    return user;
}

// Unchecked Exception: 프로그래밍 오류 또는 회복 불가능한 경우
public void transfer(Account from, Account to, BigDecimal amount) {
    if (from == null || to == null) {
        throw new IllegalArgumentException("계좌는 null일 수 없습니다."); // Unchecked
    }
    if (amount.compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgumentException("이체 금액은 0보다 커야 합니다."); // Unchecked
    }
    // 이체 로직...
}

예외 전환 (Exception Translation)

하위 계층의 예외를 상위 계층에 적합한 예외로 변환합니다.

public User findUserByEmail(String email) {
    try {
        return jdbcTemplate.queryForObject(
            "SELECT * FROM users WHERE email = ?",
            userRowMapper, email
        );
    } catch (EmptyResultDataAccessException e) {
        throw new UserNotFoundException("이메일로 사용자를 찾을 수 없음: " + email, e);
    } catch (DataAccessException e) {
        throw new RepositoryException("데이터 접근 오류", e); // 원인 보존
    }
}

Spring 트랜잭션과 예외

Spring은 기본적으로 RuntimeException(Unchecked)에서만 트랜잭션을 롤백합니다.

@Service
public class OrderService {

    @Transactional
    public void placeOrder(OrderRequest request) throws InsufficientStockException {
        // InsufficientStockException이 Checked Exception이라면
        // 트랜잭션이 롤백되지 않음!
    }

    // Checked Exception도 롤백하려면 rollbackFor 지정
    @Transactional(rollbackFor = InsufficientStockException.class)
    public void placeOrderWithRollback(OrderRequest request) throws InsufficientStockException {
        // 이제 InsufficientStockException 발생 시에도 롤백됨
    }

    // RuntimeException은 기본으로 롤백됨
    @Transactional
    public void placeOrderUnchecked(OrderRequest request) {
        // RuntimeException 발생 시 자동 롤백
        throw new IllegalStateException("재고 부족");
    }
}

비교 정리

구분Checked ExceptionUnchecked ExceptionError
상속ExceptionRuntimeExceptionError
컴파일러 강제OXX
처리 방식try-catch / throws선택적처리 불권장
발생 원인외부 환경 오류프로그래밍 오류JVM 오류
Spring 롤백X (기본)O (기본)O (기본)
예시IOExceptionNullPointerExceptionOutOfMemoryError