Backend/SpringBoot

@Controller exception 처리 (@ControllerAdvice, @RestControllerAdvice)

Dean83 2025. 9. 15. 10:59

결국, 클라이언트의 요청에 따라 오류 처리하는것이 서버 프로그래밍의 한 부분이다. 오류 처리를 위해 각 컨트롤러 별로 개별 처리를 하는 방법, 그리고 모든 컨트롤러 오류를 통합하여 처리하는 방법이 있다.  두번째 방법을 많이 쓰므로, 두번째 방법에 대한 내용을 정리하고자 한다. 

 

요약하면, @RestControllerAdvice 와 ResponseEntity 를 주로 이용하고, 이를 추천한다

 

1. 전역처리 (@ControllerAdvice)

  • Controller 들에서 발생한 예외처리를 한군데서 모아서 관리 및 처리 하고 응답한다.
  • 해당 어노테이션은 클래스 어노테이션이다. 
  • @ExceptionHandler 를 통해 특정 클래스에 발생한 오류를 다룰 수 있다. 
  • @ResponseStatus 를 통해 클라이언트에 리턴하는 에러 코드를 항상 같은값으로 유지할 수 있다.
  • @ResponseEntity 를 통해 클라이언트에 리턴하는 에러코드를 통해 상황에 따라 에러코드를 바꾸어 리턴 할 수 있다. 
  • View 리턴을 할 때(html, 타임리프)는 그냥 쓰면 되고, json 형식으로 리턴하려면 메소드에 @ResponseBody를 붙여야 한다.
  • @RestControllerAdvice 를 통해 json 전용 어노테이션을 쓸 경우 @ResponseBody를 쓸 필요 없다. (주로 사용)
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 400 잘못된 요청
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidationException(MethodArgumentNotValidException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Validation Failed");
        body.put("details", ex.getBindingResult()
                              .getFieldErrors()
                              .stream()
                              .map(e -> e.getField() + ": " + e.getDefaultMessage())
                              .toList());
        return ResponseEntity.badRequest().body(body);
    }

    // 400 잘못된 타입 (예: UUID 형식 오류)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<Map<String, Object>> handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Type Mismatch");
        body.put("details", ex.getName() + " should be of type " + ex.getRequiredType());
        return ResponseEntity.badRequest().body(body);
    }

    // 404 리소스를 찾을 수 없음
    @ExceptionHandler(NoSuchElementException.class)
    public ResponseEntity<Map<String, Object>> handleNoSuchElement(NoSuchElementException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Resource Not Found");
        body.put("details", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
    }

    // 405 지원하지 않는 메소드
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<Map<String, Object>> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Method Not Allowed");
        body.put("details", ex.getMessage());
        return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(body);
    }

    // 409 중복 등 충돌 오류
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<Map<String, Object>> handleDataIntegrityViolation(DataIntegrityViolationException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Conflict");
        body.put("details", ex.getMostSpecificCause().getMessage());
        return ResponseEntity.status(HttpStatus.CONFLICT).body(body);
    }

    // 500 서버 내부 오류 (예상치 못한 모든 오류 처리)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Internal Server Error");
        body.put("details", ex.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
    }
    
    
    @ExceptionHandler(SpecificException.class)
    public ResponseEntity<ErrorMessage> specificHandler(SpecificException ex)
    {
    	...
    	return ResponseEntity.status(HttpStatus.코드).body(new ErrorMessage("코드",ex.getMessage());
    }
}

 

 

1. 1. 특정 오류 클래스 정의하기

  • 클래스를 생성하여 Exception 클래스를 상속 받고, 생성자를 구현하면 된다.
public class SpecificException extends RuntimeException
{
	public SpecificException(String msg) {
		super(msg);
	}
}

 

2. 전역처리 - json특화 (@RestControllerAdvice)

  • Json 형식 리턴에 사용되는 특화 어노테이션. 
@RestControllerAdvice
public class GlobalRestExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Map<String, Object>> handleException(SpecificException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Internal Server Error");
        body.put("details", ex.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
    }
}

 

3. ErrorResponse 용 클래스 만들기

public record ErrorResponse(
	String code,
    String message
)
{

}

 

 

정리해보면

  • ErrorResponse 전용 DTO 생성
  • RuntimeException 을 상속 받는 커스텀 Exception 클래스 생성
  • ExceptionHandler 클래스 생성
    • @RestControllerAdvice 어노테이션을 이용하여 한데 모아서 처리할 수 있도록 선언
    • @ExceptionHandler(커스텀Exception.class) 들을 매핑할 메소드들을 각각 생성
      • 특정 오류만 처리하는 메소드들로 세분화
    • 상황에 맞는 오류코드 설정
    • ResponseEntity를 이용하여 ErrorResponse를 작성 후 리턴
  • 컨트롤러 혹은 이후 서비스 등에서 해당 상황에 맞는 오류 발생시 커스텀 Exception을 throw

하는 단계로 처리할 수 있다.