본문 바로가기

Spring

[Spring] 예외처리

자바의 예외처리 방법

자바에서는 아래와 같이 try-catch를 사용하여 예외를 처리하였다. (Java 예외처리 더보기)

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("0으로 나눌 수 없습니다.");
}
  • 한 코드 블록 안에서 직접 예외를 잡아서 처리하는 방식
  • 장점: 즉각적이고 단순한 처리 가능
  • 단점: 코드가 길어지면 여러 곳에서 같은 예외 처리 중복되며, 컨트롤러나 서비스 로직이 지저분해진다.

Spring Boot 예외처리 방법

일반 자바 애플리케이션은 예외가 터지면 예외를 직접 처리(try-catch)하여 콘솔에 에러메시지를 띄우거나, 혹은 그냥 프로그램이 강제 종료되었다.

그러나, 스프링은 주로 웹 애플리케이션을 만들 때 사용하기 때문에 예외가 발생하면, 스프링이 이를 감지하여 클라이언트에게 HTTP 응답으로 바꿔주는 과정이 필요하다. 따라서 발생하는 수많은 예외를 자동으로 감지하고, 우리가 원한다면 직접 제어할 수 있는 기능이 필요하다.

 

1) 기본 방식: @ExceptionHandler (잘 사용 안함)

Spring Boot에서 예외를 처리를 방법 중 하나는 컨트롤러 클래스 안에 직접 예외 처리 메서드를 넣는 방식이다.

@RestController
public class SampleController {

    @GetMapping("/divide")
    public int divide(@RequestParam int a, @RequestParam int b) {
        return a / b; // b가 0이면 ArithmeticException 발생
    }

    // 이 컨트롤러에서 발생하는 ArithmeticException 예외만 처리된다.
    @ExceptionHandler(ArithmeticException.class)
    public String handleArithmeticException(ArithmeticException e) {
        return "잘못된 요청입니다: " + e.getMessage();
    }
}
  • 해당 컨트롤러에서 발생한 예외만 처리
  • 따라서 다른 컨트롤러에서 예외가 나면 적용 되지 않는다.
  • 작고 단순한 앱이면 괜찮지만, 크거나 여러 컨트롤러가 있으면 불편하다.

 

2) 공통 예외 처리 @ControllerAdvice

프로젝트 전체에서 발생한 예외를 한 handler에서 공통으로 처리하는 전역 예외처리 방식이다.

@ControllerAdvice
public class GlobalExceptionHandler {
	
    // 모든 곳에서 발생한 ArithmeticException 예외가 처리된다.
    @ExceptionHandler(ArithmeticException.class)
    public ResponseEntity<String> handleArithmeticException(ArithmeticException e) {
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body("수학적 오류가 발생했습니다: " + e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception e) {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("알 수 없는 오류가 발생했습니다.");
    }
}
  • @ControllerAdvice는 모든 컨트롤러에서 발생한 예외를 감지해서 처리한다.
  • 예외 종류별로 @ExceptionHandler를 나눠서 상황에 맞게 응답을 줄 수 있다.
  • 중복 줄이고, 유지보수 편하고, 코드도 깔끔하기 때문에 보통 이 방식을 많이 사용한다.

 

ResponseStatusException

ResponseStatusException은 예외 클래스를 따로 만들지 않고, 즉시 상태 코드와 메시지를 던지고 싶을 때 사용하는 Spring의 예외 클래스이다.

@Service
public class PersonService {

    public String greet(String name) {
        if (name == null || name.isBlank()) {
            throw new ResponseStatusException(
                HttpStatus.BAD_REQUEST,"이름이 없습니다. 인사하려면 이름이 필요합니다."
            );
        }
        return name + "님, 안녕하세요!";
    }
}
  • 오류 발생 시 설정한 상태코드와 메시지가 자동으로 반환
    (오류를 처리할 핸들러가 별도로 필요하지 않다.)
  • 간단한 예외 상황에서 바로 응답하고 싶을 때 유용
  • 단, 복잡한 예외 로직 처리은 어렵다.

 

커스텀 예외처리

자바에서 기본으로 제공하는 예외 (NullPointerException, IllegalArgumentException 등)만으로는 비즈니스 로직의 의미를 명확하게 표현하기 어려운 경우, 개발자가 직접 정의한 예외 클래스를 사용할 수 있다.

  기존 예외 커스텀 예외
예외 유형 기술적인 오류 중심 도메인 의미 중심
예시 IllegalArgumentException NameRequiredException
예외 이름의 의미 어떤 상황인지 알기 어려움 예외 이름만으로도 원인 파악 가능
형태 대부분 동일한 형태 예외마다 상태코드와 메시지 지정, 처리 로직 분기 가능

 

사용자 정의 예외 클래스 (예외 정의)

public class NameRequiredException extends RuntimeException {
    public NameRequiredException(String action) {
        super("이름이 없습니다. " + action + "하려면 이름이 필요합니다.");
    }
}

 

서비스 클래스 (예외 발생)

@Service
public class PersonService {

    public String greet(String name) {
        if (name == null || name.isBlank()) {
            throw new NameRequiredException("인사");
        }
        return name + "님, 안녕하세요!";
    }
}

 

전역 예외 처리 클래스 (예외 처리)

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NameRequiredException.class)
    public ResponseEntity<String> handleNameRequired(NameRequiredException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
    }
}