Spring 예외처리 (@ExceptionHandler, @ControllerAdvice)

2025. 5. 12. 17:11·SpringBoot

 

Springboot를 공부해나가고 부트캠프에서 예외처리 관련 과제를 받아 진행을 하다보니 throw를 통한 예외처리가 아닌 직접 커스텀하여 중복되는 코드를 줄여나갈 수 있는 방법을 기록하려 합니다.

이 글을 Spring 공식 페이지를 기반으로 작성하였습니다.

(하단에 나와있는 코드는 Spring 공식 홈페이지의 코드입니다.)

 

가장 처음 작성해볼 방법은 예외 클래스나 컨트롤러 메서드에 HTTP 상태 코드를 명시적으로 지정할 수 있도록 해주는 어노테이션 @ResponseStatus를 통해서 예외처리를 하는 방법입니다.

 

1. @ResponseStatus

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")  // 404
 public class OrderNotFoundException extends RuntimeException {
     // …
 }
 
  @RequestMapping(value="/orders/{id}", method=GET)
 public String showOrder(@PathVariable("id") long id, Model model) {
     Order order = orderRepository.findOrderById(id);

     if (order == null) throw new OrderNotFoundException(id);

     model.addAttribute(order);
     return "orderDetail";
 }

 

일반적으로 웹 요청 처리 중 처리되지 않은 예외가 발생하게 되면 서버는 HTTP 500 응답을 반환하기 때문에 이를 통해 구분하여 사용자에게 반환하고 서버에 로그를 남길 수 있을 것입니다. 위와 같은 코드에서는 예외 상황에서 404에러를 반환하게 될 것입니다.

1. RuntimeException을 상속받은 예외 클래스는 내부적으로 생성자 내부에서 super("~예외입니다."); 이런식으로 처리를 하기 때문에 고정되어 있는 응답이라는 단점이라면 단점이 존재합니다.

2. @ExceptionHandler

@Controller
public class ExceptionHandlingController {

  // @RequestHandler methods
  ...

  // Exception handling methods

  // Convert a predefined exception to an HTTP Status code
  @ResponseStatus(value=HttpStatus.CONFLICT,
                  reason="Data integrity violation")  // 409
  @ExceptionHandler(DataIntegrityViolationException.class)
  public void conflict() {
    // Nothing to do
  }

  // Specify name of a specific view that will be used to display the error:
  @ExceptionHandler({SQLException.class,DataAccessException.class})
  public String databaseError() {
    // Nothing to do.  Returns the logical view name of an error page, passed
    // to the view-resolver(s) in usual way.
    // Note that the exception is NOT available to this view (it is not added
    // to the model) but see "Extending ExceptionHandlerExceptionResolver"
    // below.
    return "databaseError";
  }

  // Total control - setup a model and return the view name yourself. Or
  // consider subclassing ExceptionHandlerExceptionResolver (see below).
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception ex) {
    logger.error("Request: " + req.getRequestURL() + " raised " + ex);

    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", ex);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }
}

 

위 방법은 @ExceptionHandler를 통한 예외처리입니다.

사실 커스텀해서 나타내기 위해서는 예외클래스를 만들어야 하는 것은 동일합니다. 그렇지만 @ExceptionHandler({CustomSecondException1.class, CustomFirstException.class}) 이런식으로 동시에 일어날 수 있는 예외를 구분할 수 있는 것도 가능합니다. 

1. Controller단에서만 작동하며 주로 Controller 내부에서 비즈니스 로직은 Service를 통해서 분리되어 있을텐데 그 로직 또한 Controller 내부에서 발생한 것이기 때문에 서비스 내부에서 발생한 예외 또한 잡아줍니다.

2. ReturnType free

3. @ExceptionHandler가 적용된 메서드를 파라미터를 통해 받아올 수 있습니다.

4. 코드량이 조금 더 많습니다.

 

3. ContollerAdvice

결국 그럼 Controller단 하나하나 에러/예외를 처리해야 하는 이 불편함을 어떻게 해결해야 할까요? Spring은 그 부분도 제시합니다.

@ControllerAdvice
class GlobalControllerExceptionHandler {
    @ResponseStatus(HttpStatus.CONFLICT)  // 409
    @ExceptionHandler(DataIntegrityViolationException.class)
    public void handleConflict() {
        // Nothing to do
    }
}

 

위와 같이 @ControllerAdivce 어노테이션을 사용하여 내부에 위 형식으로 코드를 작성해준다면 전역적으로 에러를 컨트롤할 수 있습니다. 이 부분을 Enum과 예외 커스텀 클래스를 만들어서 사용해보겠습니다.

 

3-1 ErrorEnum

package com.hyun.scheduler.enums;

import org.springframework.http.HttpStatus;

public enum ErrorEnum {
  PASSWORD_MISMATCH(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."),
  SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "일정 조회에 실패했습니다.");

  private final HttpStatus httpStatus;
  private final String errorMessage;

  ErrorEnum(HttpStatus httpStatus, String errorMessage) {
    this.httpStatus = httpStatus;
    this.errorMessage = errorMessage;
  }

  public HttpStatus getHttpStatus() {
    return this.httpStatus;
  }

  public String getErrorMessage() {
    return this.errorMessage;
  }
}

 

3-2 ExceptionClass

package com.hyun.scheduler.domain.dto;

import com.hyun.scheduler.enums.ErrorEnum;
import org.springframework.http.HttpStatus;

public class MyException extends RuntimeException {
  private final HttpStatus httpStatus;

  public MyException(ErrorEnum errorEnum) {
    super(errorEnum.getErrorMessage());
    this.httpStatus = errorEnum.getHttpStatus();
  }

  public HttpStatus getHttpStatus() {
    return httpStatus;
  }
}

 

3-3 GlobalExceptionHandler

package com.hyun.scheduler.exception;

import com.hyun.scheduler.domain.dto.MyException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(MyException.class)
  public ResponseEntity<String> handleMyException(MyException ex) {
    return ResponseEntity.status(ex.getHttpStatus()).body(ex.getMessage());
  }
}

 

위와 같이 진행하는 과제 프로젝트에서는 진행을 할 수 있었습니다. Enum을 통해 예외 HttpStatus와 errorMessage를 관리하고 이를 ExceptionClass에서 반환하고 GlobalExceptionHandler에서 전역적으로 예외를 처리해줄 수 있는 로직입니다. 추후 프로젝트에서도 이 방법을 통해서 예외를 가독성 좋게 처리할 수 있다는 생각이 듭니다.

 

Reference - https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

'SpringBoot' 카테고리의 다른 글

SpringBoot 대용량 데이터 조회 (Index)  (1) 2025.07.03
AOP 탐구  (0) 2025.07.01
JPA 영속성 파헤치기  (0) 2025.06.28
SpringEvent / EDA  (2) 2025.06.24
Spring Security 없이 로그인을 하는 방법 (Session)  (0) 2025.05.07
'SpringBoot' 카테고리의 다른 글
  • AOP 탐구
  • JPA 영속성 파헤치기
  • SpringEvent / EDA
  • Spring Security 없이 로그인을 하는 방법 (Session)
vanc
vanc
공부 기록 블로그입니다.
  • vanc
    Vanc
    vanc
  • 전체
    오늘
    어제
    • 분류 전체보기 (29) N
      • Project (0)
      • SpringBoot (8)
      • Java (0)
      • 백준 (0)
      • 부트캠프 (0)
  • 블로그 메뉴

    • 홈
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    동시성이슈
    정합성
    jdbc
    N+1
    spring data elasticsearch 8
    @ExceptionHandler
    일정관리
    @converter
    @ControllerAdvice
    @BatchSize
    db
    @entitygraph
    @Enumerated
    Transactional
    REQUIRES_NEW
    fetch join
    JPA
    @ResponseStatus
    springboot
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
vanc
Spring 예외처리 (@ExceptionHandler, @ControllerAdvice)
상단으로

티스토리툴바