JAVA에서 예외가 무엇인지부터 예외 처리를 어떻게 하는지, 또 이게 실무에서 왜 중요한지에 대해서 정리해볼까 한다.
| 목차 - 에러와 예외의 차이 - 예외의 종류 - 예외 처리 방법 - 예외 처리의 중요성 |
에러와 예외의 차이
에러(Error)
메모리 부족이나 시스템 오류처럼 복구 불가능한 심각한 문제를 뜻한다. 시스템 수준에서 발생하여 프로그램 종료로 이어지는 경우가 많고, 에러는 컴파일 시점에 확인할 수 없으며 주로 실행 중에 발생한다. 즉 예측이 불가능해 개발자가 따로 코드 수정과 같은 것들로 처리할 수 없다.
ex) OutOfMemoryError, StackOverflowError
예외(Exception)
개발자가 코드를 통해 처리할 수 있는 비교적 덜 심각한 오류를 뜻한다. 정상적인 프로그램 흐름을 방해하는 이벤트로 컴파일 시점에 체크되는 경우와 실행 중에 발견되는 경우가 있다. 에러와 다르게 예측이 가능하기 때문에 'try-catch'와 같은 예외처리 구문을 통해 코드를 수정하여 수습이 가능하다.
ex ) NullPointerException, IOException, IllegalArgumentException
예외의 종류

Exception은 크게 RuntimeException과 그 외 다른 Exception으로 나눌 수 있는데, OtherException은 컴파일 시점에 확인되는 Checked Exception이고 RuntimeException은 컴파일 시점이 아닌 런타임 시점에 발생하는 Unchecked Exception이다.
즉 Checked Exception은 컴파일 시점에 확인되기 떄문에 예외 처리를 해주지 않으면 컴파일 자체가 실패해서 프로그램 실행이 되지 않고, Unchecked Exception은 컴파일 시점에는 확인이 안되기 때문에 실행 중에 에러가 터질 수 있다.
<요약>
| Checked Exception | Unchecked Exception | |
| 발생 시점 | 컴파일 시점 | 런타임 시점 |
| 예외 처리 필요 여부 | 반드시 필요 | 선택 사항 |
| 대표 클래스 | IOException, SQLException | NullPointerException, ArithmeticException |
| 원인 | 외부 요인 (파일, 네트워크 등) | 개발자 실수 (논리 오류 등) |
예외 처리 방법
그렇다면 시스템이 비정상적으로 종료되지 않게 하려면 이 예외를 처리해야 하는데 코드에서 어떻게 처리할 수 있을까? Java에서는 try-catch, throw, throws를 통해 예외를 처리한다.
try-catch
try-catch는 가장 기본적인 예외 처리 방식으로, 예외가 발생할 수 있는 코드를 try 블록 안에 작성하고 예외가 발생했을 때 수행할 동작을 catch 블록에 작성한다.
try {
int result = 10 / 0; // ArithmeticException 발생
} catch (ArithmeticException e) {
System.out.println("0으로 나눌 수 없습니다.");
} try 블록 내에서 예외가 발생하면, 해당 예외를 처리할 수 있는 catch 블록으로 제어가 이동하며 프로그램은 정상적으로 이어진다.
throw
throw는 직접 예외를 발생시킬 때 사용한다.
public void checkAge(int age) {
if (age < 19) {
throw new IllegalArgumentException("성인만 접근 가능합니다.");
}
} 즉, throw는 예외 객체를 던지는 역할을 한다. 던져진 예외는 현재 메서드를 호출한 쪽에서 처리해야 한다.
보통 실제 프로젝트에서는 @RestControllerAdvice라는 어노테이션이 선언된 Exception Handler 클래스에서 던져진 예외를 받아 화면으로 return하는 식으로 예외를 처리한다.
throws
throws는 메서드 선언부에서 이 메서드가 어떤 예외를 던질 수 있는지 명시하는 키워드다.
public void readFile(String filePath) throws IOException {
FileReader reader = new FileReader(filePath); // CheckedException
reader.read();
} throws는 이 메서드를 사용할 때 이런 예외가 발생할 수도 있다라는 사전 경고 역할을 한다. 해당 메서드를 호출한 곳에서 반드시 예외를 처리해야 컴파일이 통과된다.
try-catch + throw
실무에서는 try-catch 안에서 throw로 예외를 던지는 경우를 많이 볼 수 있다. 아래에서 얘기할 거지만 실제 설계 단계에서 예외를 어떻게 구분할지를 정하고 개발 단계에 들어가기 때문에, catch로 일단 예외를 잡고 throw로 개발자가 던지고자 하는 예외로 변환해서 던지는 것이다.
try {
userRepository.save(user);
} catch (DataIntegrityViolationException e) {
// DB 제약조건 위반 등 Spring 내부 예외를 감싸서
// 비즈니스 예외로 바꿔 던짐
throw new BusinessException("USER_DUPLICATE", "이미 존재하는 사용자입니다.");
}
예외 처리의 중요성
실제 프로젝트를 개발하다 보면 단순히 예외를 잡는 것 이상으로, 예외를 어떻게 설계하고 구분할지가 시스템 안정성과 유지보수성에 큰 영향을 미친다.
예를들어 실제 프로젝트에서는 단일 애플리케이션만 있는게 아니라 프레임워크, ESB, EDMS, 그 외 외부 연계 시스템 등 서로 다른 다수의 시스템이 연결되어 있는데, 이런 환경에서는 한 곳에서 발생한 예외가 다른 시스템에까지 영향을 줄 수 있기 때문에, 예외를 명확하게 구분하고 설계하는게 매우 중요하다.
ex)
- FrameworkException → 내부 공통 모듈에서 발생한 예외
- IntegrationException → ESB나 외부 연계 구간에서 발생한 예외
- BusinessException → 사용자의 잘못된 요청으로 발생한 예외
- SystemException → DB, 서버 자원, 네트워크 등의 시스템 문제
이렇게 예외를 구분해두면 문제가 발생했을 때 로그만 봐도 “이건 프레임워크 담당이 봐야 하는 문제구나”, “이건 외부 연계 쪽 에러니까 ESB 담당한테 전달해야겠네” 하는 식으로 담당 영역을 빠르게 특정하고 조치를 취할 수 있다.
'공개 글' 카테고리의 다른 글
| Servlet & Dispatcher-Servlet(서블릿 & 디스패처 서블릿) (0) | 2025.11.28 |
|---|---|
| 세션(Session), 쿠키(Cookie), 토큰(Token), JWT 완벽 정리 (0) | 2025.10.28 |
| Spring Batch(Tasklet)에서 트랜잭션 롤백(rollback)이 되지 않는 경우와 해결 방법 (1) | 2025.09.12 |
| Reflection과 Class 객체 (5) | 2025.07.31 |
| static (2) | 2025.05.02 |