JAVA

[JAVA] 예외(Exception) 정리

tudamoa 2025. 6. 1. 14:53

프로그래밍을 하다보면 오류는 피할 수 없는 존재이다. 오류가 발생하는 이유로는 내부적인 요인과 외부적인 요인이 있다.
오타, 코드 실수, 존재하지 않는 파일, 입력 실수, 메모리 공간 부족, 하드웨어 문제 등등 수 많은 경우가 존재한다.

 

이러한 오류는 발생 시점과 심각성을 기준으로 크게 분류할 수 있다.

 

🔍 오류의 분류 기준

1️⃣ 발생 시점에 따른 분류

분류 설명 예시
컴파일 에러 (Compile-Time Error) 코드 작성 후 컴파일 단계에서 발생 문법 오류, 잘못된 타입
런타임 에러 (Run-Time Error) 실행 도중 발생하는 오류 정수를 0으로 나누기, 배열 인덱스 초과
 

2️⃣ 심각성에 따른 분류

분류 설명 복구가능성
에러 (Error) 시스템 차원의 치명적인 문제 (개발자가 복구 불가)
예외 (Exception) 애플리케이션에서 발생 가능한 예외 상황 (개발자가 대처 가능)

 

📚 자바 예외 클래스 구조

자바에서는 모든 오류와 예외가 Throwable 클래스를 상속받는다. (가장 상위에선 Object 클래스를 상속 받고 있다.)

 

  • Throwable: 자바에서 throw할 수 있는 최상위 클래스로 오류나 예외에 대한 메시지를 담는 역할을 한다.
            getMessage(), printStackTrace() 메서드로 담긴 메시지를 출력할 수 있다.
    • Error: 시스템 레벨에서 발생하는 심각한 오류
      예) OutOfMemoryError, StackOverflowError
    • Exception: 개발자가 처리할 수 있는 예외
      • Checked Exception (컴파일 시점에 확인):
        IOException, SQLException 등
        반드시 throws 혹은 try-catch로 처리해야 한다.
      • Runtime Exception (런타임 시점에 발생, Unchecked Exception이라고도 한다.):
        NullPointerException, IndexOutOfBoundsException 등
        처리 선택은 개발자에게 맡겨진다.

 

📋 Java 예외 종류 정리

1️⃣ Checked Exception (= 컴파일 시 예외 처리 필수)

에외 클래스 설명
IOException 입출력 중 발생하는 일반적인 예외 (파일, 스트림 등 사용 시)
FileNotFoundException 존재하지 않는 파일을 열려고 할 때 발생
SQLException 데이터베이스 작업 중 SQL 관련 문제가 발생했을 때
ParseException 문자열을 날짜나 숫자로 파싱할 때 잘못된 형식일 경우
ClassNotFoundException 동적으로 로드하려는 클래스가 클래스패스에 없을 때

 

2️⃣ Unchecked Exception (= RuntimeException)

예외 클래스 설명
NullPointerException null 객체를 참조하려 할 때 발생
ArrayIndexOutOfBoundsException 배열 범위를 벗어난 인덱스 접근 시 발생
ArithmeticException 0으로 나누기 같은 수학적 오류 발생 시
IllegalArgumentException 메서드에 잘못된 인자를 전달했을 때
NumberFormatException 숫자로 변환할 수 없는 문자열을 파싱할 때
ClassCastException 잘못된 형변환 시 발생

 

 


 

⚠️ 예외(Exception)는 어떻게 처리할까?

앞서 언급했듯 에러(Error)는 개발자가 제어할 수 없는 문제지만
예외(Exception)는 충분히 예측하고 처리할 수 있는 문제이다.

따라서 Java에서는 다양한 방법으로 예외 상황을 정교하게 제어할 수 있도록 하고 있다.

 

✅ 1. try-catch(-finally) 문

가장 기본적이고 널리 사용되는 예외 처리 방식이다.

try {
    // 예외 발생 가능 코드
} catch (ExceptionType e) {
    // 예외가 발생했을 때의 처리
} finally {
    // 생략 가능, 무조건 실행되는 영역 (리소스 정리 등)
}

try 영역에서 오류가 발생하면 catch 영역으로 넘어가게 되어 예외 처리에 관한 구문을 실행시킬 수 있다.

이때 finally 영역은 예외가 발생하여도 반드시 실행되어야 하는 부분이 있다면 넣어준다.

🔍 예시:



 

✅ 2. throw / throws 키워드

예외를 직접 발생시키거나 메서드가 예외를 던질 수 있음을 명시할 때 사용한다.

throw / throws 차이점

구분 throw throws
역할 직접 예외 객체를 발생시킬 때 사용 예외를 호출한 쪽으로 전달하겠다고 선언
위치 메서드 내부 메서드 선언부
예외 처리 예외를 발생시킴 발생 가능성만 알림 (실제 발생은 아님)
사용 대상 예외 객체 단일 여러 예외도 나열 가능

 

🔍 예시 1: throw

public class Main {
    public static void main(String[] args) {
        int age = -5;

        if (age < 0) {
            throw new IllegalArgumentException("나이는 음수가 될 수 없습니다.");
        }

        System.out.println("정상적인 나이입니다.");
    }
}

 

  • 메서드 내부에서 throw를 선언하였다.
  • throw는 예외 객체를 생성해서 던진다.

 

 

🔍 예시 2: throws

import java.io.*;

public class Main {

    public static void main(String[] args) {
        try {
            readFile();  // 예외 발생 가능
        } catch (IOException e) {
            System.out.println("파일 처리 중 예외 발생: " + e.getMessage());
            // 파일 처리 중 예외 발생: test.txt (지정된 파일을 찾을 수 없습니다)
        }
    }

    // 여기서 예외를 처리하지 않고 throws로 위임
    public static void readFile() throws IOException {
        FileInputStream fis = new FileInputStream("test.txt"); // 파일이 없으면 예외 발생
        fis.read();
        fis.close();
    }
}

 

  • readFile() 메서드는 IOException을 바로 처리하지 않고 호출한 쪽(main)에 맡긴다.
  • 호출부에서는 반드시 try-catch 또는 다시 throws를 선언하여 예외를 처리해야 한다.

 

☑️ 언제 어떤 방식으로 써야할까?

상황  사용 방식 설명
예외 직접 발생 throw 개발자가 인위적으로 예외를 만들고 던질 때
예외를 위로 전파 throws 메서드에서 예외가 발생할 수 있음을 알릴 때
즉시 처리 try-catch 예외가 발생했을 때 그 자리에서 바로 처리

 


 

 

 

🔁 예외 변환(Exception Translation)이란?

하나의 예외를 catch 한 뒤, 다른 예외(ex. checked->uncheked)로 바꾸어 던지는 것이다.

 

🎯 왜 예외를 변환할까?

목적 설명
명확성 원래 예외보다 호출자에게 더 직관적인 예외를 전달할 수 있음
코드 간결화 매번 try-catch 또는 throws를 쓰는 번거로움 제거
캡슐화 하위 계층(예: DAO, Repository)의 세부 예외를 상위 계층에 노출하지 않음

 

💡 예시: IOException → RuntimeException으로 변환

import java.io.*;

public class FileService {

    public String readFile(String filePath) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            return reader.readLine();
        } catch (IOException e) {
            // 예외를 직접 처리하지 않고, unchecked 예외로 감싸서 던짐
            throw new RuntimeException("파일 읽기 중 오류 발생", e);
        }
    }
}

 

장점 

  • 호출하는 쪽에서 throws IOException 없이도 호출 가능
  • 예외가 발생하면 RuntimeException으로 전파됨 → 나중에 전체적으로 처리 가능

 


✅ 커스텀 예외(Custom Exception)란?  

자바에서 기본 제공되는 예외 클래스 외에 개발자가 직접 정의한 예외 클래스이다.

 

  •  커스텀 예외를 사용하면 상황 파악, 서비스 로직에 맞는 예외를 구성 가능하고 Spring의 @ControllerAdvice와 결합할 수 있다.

1️⃣ Checked Exception으로 만들기

public class MyCheckedException extends Exception {
    public MyCheckedException(String message) {
        super(message);
    }
}
  • Exception을 상속하면 Checked Exception이 된다.
  • 사용 시 throws 또는 try-catch로 반드시 처리해야 한다.

2️⃣ Unchecked Exception으로 만들기

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

    public InvalidUserInputException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • RuntimeException을 상속하면 Unchecked Exception이 된다.
  • try-catch 없이도 호출 가능하고 필요할 때만 처리할 수 있다.

 

💡 커스텀 예외 사용 예시