본문 바로가기
Spring/관련 지식

Spring에서의 예외 처리 (+ 커스텀 에러 구현)

by 정구정구 2026. 7. 2.

Spring에서의 예외 처리

자바의 try-catch문을 대신해서, 스프링에서의 핸드링 방식을 정리해 보았다.

 

1. Spring 기본 예외 처리

2. @ExceptionHandler

3. @RestControllerAdvice

 

이렇게 3가지가 존재하고, 동시에 존재할 경우 위의 3,1,2 순서대로 우선순위를 가진다. 

 

1. Spring 기본 예외 처리 (비권장)

    @Transactional(readOnly = true)
    public GetScheduleResponse getOne(Long id) {
        Schedule schedule = scheduleRepository.findById(id).orElseThrow(
                ()->new IllegalStateException("존재하지 않는 일정 입니다.")
        );
        
        return new GetScheduleResponse(
                schedule.getId(),
                schedule.getTitle(),
                schedule.getContents(),
                schedule.getAuthorName(),
                schedule.getCreatedAt(),
                schedule.getModifiedAt()
        );
    }

 

 

 위의 예제 코드 이후에 따로 핸들러를 구현하지 않으면 기본 에러 처리 로직을 실행한다. 다만 위와 같이 존재하지 않는 일정을 요청한 클라이언의 잘못이기 때문에 4xx 에러를 발생시켜야 하지만, 에러는 500 Internal Server Error가 출력한다.

 

2. @ExceptionHandler - 컨트롤러 별 예외 처리 (비권장)

@RestController
@RequiredArgsConstructor
public class ScheduleController {

    private final ScheduleService scheduleService;

    // 일정 단건 조회
    @GetMapping("/schedules/{id}")
    public ResponseEntity<GetScheduleResponse> getOne(
            @PathVariable Long id) {
        GetScheduleResponse result = scheduleService.getOne(id);
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    // 예외 처리 메서드
    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<String> handlerIllegalStateException(IllegalStateException e) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body("요청 오류: " + e.getMessage());
    }
}

 

 컨트롤러마다 ExceptionHandler를 설정하면 해당 컨트롤러에서 발생하는 에러에 대한 핸들링을 실행해준다. 하지만 이 방법은 코드 중복이 심해지기 때문에 잘 사용하지 않는다.

 

 

3. @RestControllerAdvice - 전역 예외 처리 (권장)

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<String> handleIllegalStateException(IllegalStateException e) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body("요청 오류: " + e.getMessage());
    }
}

 

 사실상 Spring에서 표준으로 쓰이는 방식이며, 위와 같이 선언하면 전역적으로 발생한 IllegalStateException에 대한 에러를 핸들링 해줄 수 있다.

 

 

커스텀 에러

 IllegalStateException 같은 공통적으로 사용되는 에러로만 예외를 처리하게 되면, 나중에 코드를 볼 때 이게 왜 발생했는지 한 눈에 파악하기 어려울 수 있다. 상황에 맞는 이름을 커스텀 에러를 구현하면 나중에 유지보수에 도움이 된다.

 

커스텀 에러 구현

커스텀 에러 구현 

// extends RuntimeException 중요!
public class ScheduleNotFoundException extends RuntimeException {
    public ScheduleNotFoundException(String message) {
        super(message);
    }
}

 

 

커스텀 에러 핸들러 구현

@RestControllerAdvice
public class GlobalExceptionHandler {

    // ScheduleNotFoundException 커스텀 에러 핸들링
    @ExceptionHandler(ScheduleNotFoundException.class)
    public ResponseEntity<ErrorResponse> HandleScheduleNotFoundException(ScheduleNotFoundException ex) {
        ErrorResponse response = new ErrorResponse(ex.getMessage());
        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(response);
    }
}

 

 

커스텀 에러 사용 예제

 

    @Transactional(readOnly = true)
    public GetScheduleResponse getOne(Long id) {
        Schedule schedule = scheduleRepository.findById(id).orElseThrow(
                ()->new ScheduleNotFoundException("존재하지 않는 일정 입니다.")
        );
        
        return new GetScheduleResponse(
                schedule.getId(),
                schedule.getTitle(),
                schedule.getContents(),
                schedule.getAuthorName(),
                schedule.getCreatedAt(),
                schedule.getModifiedAt()
        );
    }

 

 

 

공통 부모 커스텀 에러 구현

 위와 같이 커스텀 에러를 구현하면, 커스텀 에러 하나하나에 모두 핸들러를 구현해줘야 한다. 공통 부모 커스텀 에러를 구현하면 핸들러 하나로 모든 커스텀 에러를 처리할 수 있다.

 

공통 부모 커스텀 에러 구현 

import lombok.Getter;

@Getter
public class CustomeException extends RuntimeException {

    private final HttpStatus status;

    public CustomeException(HttpStatus status, String message) {
        super(message);
        this.status = status;
    }
}

 

 

공통 부모 커스텀 에러를 상속 받는 커스텀 에러 구현

// extends ServerException 중요!
public class ScheduleNotFoundException extends CustomeException {
    public ScheduleNotFoundException(String message) {
        super(HttpStatus.NOT_FOUND, message); // HttpStatus.NOT_FOUND 지정
    }
}

 

 

공통 부모 커스텀 에러 핸들러 구현

@RestControllerAdvice
public class GlobalExceptionHandler {

    // ScheduleNotFoundException 커스텀 에러 핸들링
    @ExceptionHandler(CustomeException.class)
    public ResponseEntity<String> handleCustomException(CustomeException ex) {
        return ResponseEntity
                .status(ex.getStatus())
                .body(ex.getMessage());
    }
}

 

위와 같이 구현하면,  ServiceException를 상속받는 모든 커스텀 에러를 GlobalExceptionHandler가 처리해줄 수 있다.