자바 백엔드 입문 7편 — 자바 예외 처리

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

자바 백엔드 입문 7편. try-catch·throw·체크드 예외와 언체크드 예외의 정확한 차이까지 자바 예외 처리를 화재 경보 비유로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 7편 — 자바 예외 처리

이 글은 자바 백엔드 입문 시리즈 59편 중 7편이에요. 이번 7편은 자바 코드를 안정적으로 굴리는 핵심 — 자바 예외 처리 를 풀어 가요.

예외 처리가 헷갈리는 이유

자바 코드 보면 try·catch·finally·throw·throws 같은 키워드가 등장해요. 또 "체크드 예외"·"언체크드 예외" 라는 용어. 그리고 어떤 예외는 "꼭 잡아야 하고" 어떤 건 "안 잡아도 되고" — 룰이 복잡해요.

이 글에서는 화재 경보 비유로 풀어요. 예외 발생 = "화재 경보 울림", try-catch = "경보 잡고 진화", throw = "경보 직접 울리기". 끝까지 따라오시면 자바 예외 처리의 전체 그림이 한 번에 잡혀요.

예외란 무엇인가

코드 실행 중 "비정상 상황" 이 발생할 때 — 자바는 예외(Exception) 라는 객체를 던져요.

int[] arr = new int[3];
System.out.println(arr[10]);   // ArrayIndexOutOfBoundsException 발생

예외 발생 시 — 기본적으로 프로그램 종료. 단 try-catch 로 잡으면 "진화" 가능.

try-catch — 기본 구조

try {
    int result = 10 / 0;                          // ArithmeticException 발생
} catch (ArithmeticException e) {
    System.out.println("0으로 못 나눠요: " + e.getMessage());
}
System.out.println("프로그램 계속");                 // 실행됨

3단계: 1. try 블록 — 예외 발생 가능 영역 2. catch 블록 — 예외 타입별 처리 3. 예외 잡힘 → catch 실행 → 이후 코드 정상 실행

여러 catch — 예외 종류별

try {
    file = openFile("data.txt");
    int n = Integer.parseInt(file.readLine());
} catch (IOException e) {                          // 파일 관련
    log.error("파일 읽기 실패", e);
} catch (NumberFormatException e) {                // 숫자 파싱 실패
    log.error("숫자 형식 아님", e);
} catch (Exception e) {                            // 그 외 모든 예외 (최후 안전망)
    log.error("알 수 없는 에러", e);
}

위에서 아래로 매칭 — 첫 매치 catch만 실행. 구체 → 일반 순서로 박아야 함. Exception e 를 맨 위에 박으면 그 아래 catch는 영영 도달 불가.

finally — 무조건 실행

FileReader reader = null;
try {
    reader = new FileReader("data.txt");
    // 작업
} catch (IOException e) {
    log.error("파일 에러", e);
} finally {
    if (reader != null) {
        reader.close();                            // 예외 났든 안 났든 무조건 닫기
    }
}

finally = "try 통과·예외 발생 모두 무조건 마지막에 실행". 자원 정리(파일·DB 연결 close)에 표준 패턴. 단 — 더 깔끔한 try-with-resources가 있어요.

try-with-resources — 자동 close (자바 7+)

try (FileReader reader = new FileReader("data.txt")) {
    // 작업
} catch (IOException e) {
    log.error("파일 에러", e);
}
// reader.close() 자동 호출

괄호 안에 박힌 자원은 — 블록 끝나면 자동으로 close() 호출. finally 박을 필요 없음. AutoCloseable 인터페이스 구현한 객체(FileReader·BufferedReader·DB Connection 등)면 OK.

한국 회사 표준 = try-with-resources. finally 명시는 옛 스타일.

throw — 예외 직접 던지기

public void withdraw(int amount) {
    if (amount <= 0) {
        throw new IllegalArgumentException("금액은 양수여야 합니다");
    }
    if (amount > balance) {
        throw new IllegalStateException("잔액 부족");
    }
    balance -= amount;
}

throw = "내가 예외를 직접 발생". 비즈니스 룰 위반 시 자주.

throws — 메서드 시그니처에 선언

public String readFile(String path) throws IOException {       // ← throws 선언
    FileReader reader = new FileReader(path);                  // IOException 발생 가능
    return reader.readLine();
}

// 호출자
public void process() {
    try {
        String content = readFile("data.txt");                 // 무조건 try-catch
    } catch (IOException e) {
        log.error("실패", e);
    }
}

throws IOException = "이 메서드는 IOException 던질 수 있으니 호출자가 잡아라". 체크드 예외일 때만 필요.

체크드 vs 언체크드 — 가장 헷갈리는 부분

자바 예외는 두 종류.

체크드 예외 (Checked Exception)

  • Exception 직접 상속 (RuntimeException 제외)
  • 예: IOException·SQLException·InterruptedException
  • 컴파일러가 강제 — 메서드에 throws 선언 또는 try-catch 필수
  • 안 잡으면 컴파일 에러
  • 의미 = "외부 자원 관련 — 처리해야 정상 동작 가능"

언체크드 예외 (Unchecked Exception)

  • RuntimeException 상속
  • 예: NullPointerException·ArrayIndexOutOfBoundsException·IllegalArgumentException
  • 컴파일러가 강제 안 함
  • try-catch 안 해도 컴파일 통과 (런타임에 폭발할 뿐)
  • 의미 = "프로그래머 실수 — 코드 자체를 고쳐야 함"

사용 룰

상황 추천
외부 자원 (파일·DB·네트워크) 체크드 예외 (또는 처리 후 RuntimeException으로 래핑)
잘못된 매개변수 IllegalArgumentException (언체크드)
잘못된 상태 IllegalStateException (언체크드)
비즈니스 룰 위반 커스텀 RuntimeException (언체크드)
Spring 환경 거의 99% 언체크드

모던 자바 흐름 = 언체크드 우세. 체크드 예외는 "안 잡으면 컴파일 통과 안 됨" 강제가 오히려 코드 더럽힘 (throws SQLException, IOException, ... 줄줄). Spring도 거의 모든 예외를 RuntimeException 자식으로 설계.

커스텀 예외 만들기

비즈니스 도메인 예외는 직접 정의.

public class InsufficientBalanceException extends RuntimeException {     // 언체크드
    private final long currentBalance;

    public InsufficientBalanceException(long currentBalance) {
        super("잔액 부족: 현재 " + currentBalance);
        this.currentBalance = currentBalance;
    }

    public long getCurrentBalance() {
        return currentBalance;
    }
}

// 사용
public void withdraw(long amount) {
    if (amount > balance) {
        throw new InsufficientBalanceException(balance);
    }
    balance -= amount;
}

비즈니스 의미가 명확한 예외 — 디버깅·로그 한결 깔끔.

예외 체이닝 — 원인 추적

원인 예외를 감싸 다시 던질 때:

try {
    repository.save(order);
} catch (DataAccessException e) {
    throw new OrderSaveFailedException("주문 저장 실패", e);   // ← 원인 예외 전달
}

new OrderSaveFailedException(msg, cause) 두 번째 인자로 원인 예외 전달. 로그에 원본 stack trace 보존돼서 디버깅 한결 쉬워요.

Spring에서 예외 처리 — @ControllerAdvice

이 시리즈 30편 @ExceptionHandler 에서 깊이 — 컨트롤러 전반의 예외를 한 곳에 모아 처리.

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(InsufficientBalanceException.class)
    public ResponseEntity<ErrorResponse> handle(InsufficientBalanceException e) {
        return ResponseEntity.badRequest()
                .body(new ErrorResponse("INSUFFICIENT_BALANCE", e.getMessage()));
    }
}

비즈니스 예외를 도메인 곳곳에 던지면 — 공통 핸들러가 HTTP 응답으로 변환. Spring 백엔드의 표준.

⚠️ 자주 틀리는 함정

예외를 잡고 아무 일도 안 하는 것이 가장 위험. catch (Exception e) { } 같은 빈 catch가 박혀 있으면 — 시스템 에러가 묵살돼 디버깅 불가. 최소한 log.error("msg", e) 박기.

한 줄 정리 — 자바 예외 처리 = try-catch-finally + throw/throws + 체크드 vs 언체크드 구분. 모던 자바는 언체크드 우세. try-with-resources 표준. Spring은 @ControllerAdvice로 통합 처리.

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

  • 예외 = 비정상 상황 발생 시 던지는 객체
  • try-catch = 기본 처리 구조
  • catch 순서 = 구체 → 일반 (Exception을 위에 박으면 X)
  • finally = 무조건 마지막 실행 (자원 정리)
  • try-with-resources = 자동 close, 한국 회사 표준 (자바 7+)
  • throw = 예외 직접 던지기
  • throws = 메서드 시그니처에 "이 예외 던진다" 선언
  • 체크드 예외 → throws 또는 try-catch 강제
  • 언체크드 예외 → 컴파일러 강제 X
  • 체크드 = IOException·SQLException·InterruptedException
  • 언체크드 = NullPointerException·IllegalArgumentException·RuntimeException 자식
  • 모던 자바 = 언체크드 우세 — Spring 99% 언체크드
  • 커스텀 예외 = extends RuntimeException
  • 예외 체이닝 = new XxxException(msg, cause) 로 원인 보존
  • 빈 catch 절대 금지 — 최소 log.error("msg", e)
  • e.getMessage() = 예외 메시지
  • e.printStackTrace() = 디버깅용 (운영은 log 사용)
  • e.getCause() = 원인 예외
  • Spring @ControllerAdvice = 전역 예외 핸들러
  • 컨트롤러 단에서 예외 → HTTP 응답으로 변환
  • 자바 백엔드 = 예외 잘 다루는 일

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

이전 글:

다음 글:

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

답글 남기기

error: Content is protected !!