본문으로 건너뛰기

8.1 예외 처리 소개 (Exception Handling Overview)

노트

이 가이드는 Java 21 버전을 기준으로 작성되었습니다. 최신 규격은 공식 Java Documentation을 참고하세요.

프로그램을 만들다 보면 필연적으로 다양한 오류를 만나게 됩니다. 사용자가 숫자를 입력해야 하는 곳에 문자를 입력하거나, 읽어야 할 파일이 삭제되어 없거나, 네트워크 연결이 끊어지는 등 다양한 예외 상황이 발생할 수 있습니다.

자바에서 예외 처리(Exception Handling) 란, 이처럼 프로그램 실행 중 발생할 수 있는 예기치 못한 에러 상황에 대비한 코드를 미리 작성하여, 프로그램의 비정상적인 종료를 막고 정상적인 실행 상태를 유지 하도록 하는 필수적인 작업입니다.

1. 에러(Error) vs 예외(Exception)

자바에서는 프로그램 실행 시 발생할 수 있는 문제를 크게 두 가지로 분류합니다.

에러(Error)

메모리 부족(OutOfMemoryError)이나 스택 오버플로우(StackOverflowError)처럼 시스템 자체가 마비될 정도로 심각하고 복구할 수 없는 수준의 오류입니다. 발생하면 프로그램의 비정상 종료를 막을 방법이 없으므로, 개발자가 코드 레벨에서 수습할 수 있는 영역이 아닙니다.

// StackOverflowError 예시 - 재귀 호출이 끝없이 이어질 때 발생
public class ErrorExample {
public static void infiniteRecursion() {
infiniteRecursion(); // 자기 자신을 끊임없이 호출 → StackOverflowError!
}

public static void main(String[] args) {
infiniteRecursion();
}
}
// 실행 결과: Exception in thread "main" java.lang.StackOverflowError

예외(Exception)

사용자의 잘못된 조작이나 개발자의 코딩 실수로 인해 발생하는 다소 가벼운 형태의 오류입니다. 예외 처리 구문(try-catch)을 통해 코드 레벨에서 충분히 수습 및 복구 가 가능합니다.

우리가 공부하고 코드로 방어해야 하는 부분은 바로 이 예외(Exception) 입니다.

2. 예외 클래스의 계층 구조

자바에서는 모든 예외와 에러도 객체로 취급되며, 최상위 조상인 java.lang.Object 클래스를 상속받습니다.

Object
└── Throwable
├── Error ← 시스템 수준 심각한 오류 (처리 불가)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception ← 프로그램 수준 오류 (처리 가능)
├── IOException ← Checked Exception
├── SQLException ← Checked Exception
├── ClassNotFoundException ← Checked Exception
└── RuntimeException ← Unchecked Exception
├── NullPointerException
├── ArrayIndexOutOfBoundsException
├── ClassCastException
├── NumberFormatException
└── ArithmeticException

3. Checked Exception vs Unchecked Exception

예외 클래스들은 크게 두 부류로 나뉩니다.

구분Checked ExceptionUnchecked Exception
상속Exception 직계 자손RuntimeException 자손
컴파일러 강제예외 처리 필수 (안 하면 컴파일 에러)예외 처리 선택 사항
주요 원인외부 환경 (파일 없음, 네트워크 오류)프로그래머 실수 (null 접근, 잘못된 형변환)
대표 예시IOException, SQLExceptionNullPointerException, ClassCastException
import java.io.FileReader;
import java.io.IOException;

public class CheckedExample {
public static void main(String[] args) {
// Checked Exception: 컴파일러가 반드시 처리를 요구합니다
// try-catch 없이 쓰면 컴파일 에러 발생!
try {
FileReader reader = new FileReader("없는파일.txt");
} catch (IOException e) {
System.out.println("파일을 찾을 수 없습니다: " + e.getMessage());
}
}
}

4. 자주 발생하는 예외 목록

실무에서 가장 자주 만나게 되는 예외들을 미리 알아두면 디버깅 시간을 크게 줄일 수 있습니다.

NullPointerException (NPE)

null인 참조 변수의 멤버를 접근할 때 발생합니다. 자바 개발자가 가장 많이 만나는 예외입니다.

public class NullPointerExam {
public static void main(String[] args) {
String name = null;

// null인 변수에 메서드를 호출하면 NullPointerException 발생!
try {
int len = name.length(); // 에러 발생!
} catch (NullPointerException e) {
System.out.println("name이 null입니다. 초기화가 필요합니다.");
// Java 14+: e.getMessage()가 더 자세한 원인을 알려줍니다
}

// 방어적 코딩: null 체크 후 호출
if (name != null) {
System.out.println("길이: " + name.length());
}
}
}

ArrayIndexOutOfBoundsException

배열의 유효 범위를 벗어난 인덱스에 접근할 때 발생합니다.

public class ArrayExam {
public static void main(String[] args) {
int[] arr = {10, 20, 30}; // 인덱스 0, 1, 2

try {
System.out.println(arr[5]); // 인덱스 5는 없음! 에러 발생
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("배열 범위를 벗어났습니다: " + e.getMessage());
}

// 올바른 배열 순회
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}

NumberFormatException

문자열을 숫자로 변환할 때 변환 불가능한 형식이면 발생합니다.

public class NumberFormatExam {
public static void main(String[] args) {
String[] inputs = {"123", "abc", "45.6", ""};

for (String input : inputs) {
try {
int number = Integer.parseInt(input);
System.out.println("변환 성공: " + number);
} catch (NumberFormatException e) {
System.out.println("변환 실패 [" + input + "]: 숫자 형식이 아닙니다");
}
}
}
}

실행 결과:

변환 성공: 123
변환 실패 [abc]: 숫자 형식이 아닙니다
변환 실패 [45.6]: 숫자 형식이 아닙니다
변환 실패 []: 숫자 형식이 아닙니다

ClassCastException

형변환이 불가능한 타입으로 강제 변환할 때 발생합니다.

public class ClassCastExam {
public static void main(String[] args) {
Object obj = "Hello"; // String 객체를 Object 타입으로 저장

try {
// String을 Integer로 형변환 시도 → 불가능!
Integer num = (Integer) obj;
} catch (ClassCastException e) {
System.out.println("형변환 실패: " + e.getMessage());
}

// instanceof로 미리 확인하는 안전한 방법
if (obj instanceof String str) { // Java 16+ 패턴 매칭
System.out.println("문자열 길이: " + str.length());
}
}
}

5. 예외 처리가 중요한 이유

예외 처리의 3가지 핵심 이유
  1. 프로그램 안정성: 언제 터질지 모르는 치명적인 버그로부터 프로그램이 죽지 않도록 방탄(Bullet-proof) 코드를 만듭니다.
  2. 사용자 경험: 에러가 발생했을 때 무서운 스택 추적(Stack Trace) 대신 "현재 서비스를 이용할 수 없습니다"처럼 친절한 안내 메시지를 출력할 수 있습니다.
  3. 디버깅 지원: 문제 발생 시 관련 로그(Log)를 안전하게 기록하여 개발자가 추후 버그를 쉽게 수정하도록 돕습니다.
// 예외 처리 없는 코드 (위험)
public class UnsafeCode {
public static void main(String[] args) {
String input = "abc";
int number = Integer.parseInt(input); // 예외 발생 → 프로그램 죽음!
System.out.println("결과: " + number); // 절대 실행 안 됨
}
}

// 예외 처리 있는 코드 (안전)
public class SafeCode {
public static void main(String[] args) {
String input = "abc";
try {
int number = Integer.parseInt(input);
System.out.println("결과: " + number);
} catch (NumberFormatException e) {
System.out.println("올바른 숫자를 입력해주세요.");
// 프로그램은 계속 실행됩니다!
}
System.out.println("프로그램이 정상적으로 계속 실행됩니다.");
}
}

6. 예외 처리 방법 3가지

자바에서 예외를 처리하는 방법은 크게 세 가지입니다.

방법문법설명
try-catch로 직접 처리try { } catch { }예외가 발생한 곳에서 바로 처리
throws로 책임 전가void method() throws IOException상위 호출자에게 처리 위임
throw로 직접 발생throw new Exception()의도적으로 예외 객체를 생성해 던짐
import java.io.IOException;

public class ExceptionMethodsExample {

// 방법 2: throws로 예외 처리를 호출자에게 위임
public static void readFile(String path) throws IOException {
// 이 메서드는 예외를 직접 처리하지 않고 호출한 쪽에 넘김
throw new IOException("파일을 읽을 수 없습니다: " + path);
}

// 방법 3: throw로 직접 예외 발생
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다");
}
return a / b;
}

public static void main(String[] args) {
// 방법 1: try-catch로 직접 처리
try {
readFile("없는파일.txt"); // throws IOException 메서드 호출
} catch (IOException e) {
System.out.println("파일 오류: " + e.getMessage());
}

try {
int result = divide(10, 0);
} catch (ArithmeticException e) {
System.out.println("계산 오류: " + e.getMessage());
}

System.out.println("프로그램이 정상 종료됩니다.");
}
}

실행 결과:

파일 오류: 파일을 읽을 수 없습니다: 없는파일.txt
계산 오류: 0으로 나눌 수 없습니다
프로그램이 정상 종료됩니다.

7. 예외 정보 확인 메서드

예외 객체(e)를 통해 오류의 세부 정보를 확인할 수 있습니다.

public class ExceptionInfoExample {
public static void main(String[] args) {
try {
int[] arr = new int[3];
arr[10] = 100; // ArrayIndexOutOfBoundsException 발생
} catch (ArrayIndexOutOfBoundsException e) {
// 1. getMessage(): 예외의 간단한 메시지 반환
System.out.println("메시지: " + e.getMessage());

// 2. toString(): 예외 클래스명 + 메시지
System.out.println("toString: " + e.toString());

// 3. printStackTrace(): 예외 발생 경로 전체를 출력 (디버깅 필수)
System.out.println("--- 스택 추적 ---");
e.printStackTrace();

// 4. getClass().getName(): 예외 클래스 이름
System.out.println("예외 클래스: " + e.getClass().getName());
}
}
}
주의

printStackTrace()는 개발/디버깅 단계에서는 매우 유용하지만, 운영(Production) 환경에서는 민감한 시스템 정보가 노출될 수 있으므로 로그 프레임워크(Logback, Log4j 등)를 통해 로그 파일에 기록하는 것을 권장합니다.

8. 실전 예제: 사용자 입력 안전하게 처리하기

import java.util.Scanner;

public class SafeInputExample {

// 문자열을 안전하게 정수로 변환하는 유틸 메서드
public static int parseIntSafe(String input, int defaultValue) {
try {
return Integer.parseInt(input.trim());
} catch (NumberFormatException e) {
System.out.println("경고: '" + input + "'은 유효한 숫자가 아닙니다. 기본값 " + defaultValue + " 사용.");
return defaultValue;
} catch (NullPointerException e) {
System.out.println("경고: null 값입니다. 기본값 " + defaultValue + " 사용.");
return defaultValue;
}
}

public static void main(String[] args) {
String[] testInputs = {"42", " 100 ", "abc", null, "3.14", "999"};

int total = 0;
for (String input : testInputs) {
int value = parseIntSafe(input, 0);
total += value;
System.out.println("입력: " + input + " → 변환: " + value);
}

System.out.println("합계: " + total);
}
}

실행 결과:

입력: 42 → 변환: 42
입력: 100 → 변환: 100
경고: 'abc'은 유효한 숫자가 아닙니다. 기본값 0 사용.
입력: abc → 변환: 0
경고: null 값입니다. 기본값 0 사용.
입력: null → 변환: 0
경고: '3.14'은 유효한 숫자가 아닙니다. 기본값 0 사용.
입력: 3.14 → 변환: 0
입력: 999 → 변환: 999
합계: 1141
고수 팁

실무에서는 예외를 단순히 catch (Exception e)로 뭉뚱그려 잡는 것은 안티패턴입니다. 가능한 한 구체적인 예외 타입 을 명시하여 각 상황에 맞는 처리를 하는 것이 좋은 코드입니다. 또한 catch 블록에서 아무것도 하지 않는 "빈 catch 블록"은 버그를 숨기는 최악의 습관입니다.

다음 장에서는 자바에서 예외를 통제하는 가장 기본이자 핵심적인 무기인 try-catch 블록에 대해 자세히 알아보겠습니다.