Java

spring 예외(Exception) 처리

blackbearwow 2025. 4. 17. 18:05

사용자가 필수 파라미터를 빼고 요청을 보냄. 서버 내에서 데이터베이스 조회 오류. 그 외 예상치 못한 오류. 등등 예외가 발생하면 어떻게 처리해야 하는가? 기본적으로 스프링이 적절한 예외 페이지를 보여준다. 그러나 좀 더 자세하게 오류 정보를 전달하고시다면 따로 설정해주어야 한다.

예외가 발생하였을 때, 표현 방법에 따라 2가지로 나눌 수 있다. 웹에서 요청한 경우와 api를 요청한 경우이다. 각각 html, json으로 오류 정보를 응답한다.

1. html로 오류 표시 (웹 클라이언트 요청)

스프링에서는 resources/templates/error또는 resources/static/error디렉토리에 에러코드.html을 만들면 된다. 

 

resources/templates/error/500.html    resources/templates/error/5xx.html
resources/static/error/400.html    resources/static/error/404.html

등등을 만들면 되는 것이다. 

 

오류 페이지의 model에 timestamp, path, status, message, error, exception, errors, trace값들을 전달해주기 때문에 정보를 출력해줄 수 있다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>4xx 에러 </title>
</head>
<body>
<p th:text="|timestamp = ${timestamp}|"></p>
<p th:text="|path = ${path}|"></p>
<p th:text="|status = ${status}|"></p>
<p th:text="|message = ${message}|"></p>
<p th:text="|error = ${error}|"></p>
<p th:text="|exception = ${exception}|"></p>
<p th:text="|errors = ${errors}|"></p>
<p th:text="|trace = ${trace}|"></p>
</body>
</html>

resources/templates/error/4xx.html

위 그림과 같이 표시된다. 기본적으로 보안상 message, exception, errors, trace는 전달되지 않는다. 전달하고 싶다면 application.properties에서 설정해주면 된다. 그러나 보통 해커들에게 정보를 주는것을 막기 위해 정보들을 잘 주지 않는다.

 

서버 내에서 예외가 생겼을 경우, status 500으로 보인다. 그러나 해당 예외에 맞는 자세한 상태코드를 응답하고싶다면 @ExceptionHandler와 @ResponseStatus를 사용하면 된다.

@Controller
public class htmlController {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgumentException(IllegalArgumentException ex) {
        return "error/400";
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public String handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
        return "error/missingRequestParameter";
    }

    @GetMapping("/aa")
    public String aa() {
        throw new IllegalArgumentException("잘못된 매개변수");
    }

    @GetMapping("/bb")
    public String bb(@RequestParam String id) {
        return "index";
    }
}

/aa경로에서 어떤 오류가 생겨 IllegalArgumentException이 발생한다고 해보자. 그러면 원래는 사용자 화면에 status 500가 보이게 된다. 이 예외가 발생했을 때, status 400를 전달하고싶다면 @ExceptionHandler에 예외를 넣고 @ResponseStatus에 status를 등록하면 된다. 

/bb경로는 id라는 파라미터를 받는데, 이 파라미터가 없다면 MissingServletRequestParameterException 예외가 발생하며 status 400을 응답한다. 그러나 파라미터가 없다는 정확한 정보전달을 위해 따로 페이지를 만들어 전달한다면 위와 같이 만들 수 있다.

2. json으로 오류 표시 (api 요청)

api요청은 보통 json으로 주고받는다. 예외가 발생했다면 오류 응답 스펙에 맞춰 클래스를 하나 생성해준다.

@AllArgsConstructor
@Getter @Setter
public class ErrorResult {
    private String status;
    private String message;
}

그리고 @ExceptionHandler와 @ResponseStatus를 사용하여 적절한 응답을 만들면 된다.

@RestController
@RequestMapping("/api")
public class ApiController {

    @ResponseStatus(code= HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalArgumentException(IllegalArgumentException e) {
        return new ErrorResult("bad", e.getMessage());
    }

    @GetMapping("/ex")
    public String ex() {
        throw new RuntimeException("ex");
    }

    @GetMapping("/bad")
    public String bad() {
        throw new IllegalArgumentException("잘못된 입력 값");
    }

    @GetMapping("/param")
    public String param(@RequestParam String param) {
        return "ok";
    }
}

ApiController.class

{
    "timestamp": "2025-04-17T09:01:48.929+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/api/ex"
}

/api/ex 응답

{
    "status": "bad",
    "message": "잘못된 입력 값"
}

/api/bad응답

 

이렇게 @ExceptionHandler를 사용하면 컨트롤러 단위로 예외를 처리할 수 있다.

@ControllerAdvice 또는 @RestControllerAdvice를 사용해 여러 컨트롤러 단위로 예외를 처리할 수 있다. html에 @ControllerAdvice를, api에 @RestControllerAdvice를 지정하면 된다.

 

@RestControllerAdvice(annotations = RestController.class)
public class ExRestControllerAdvice {

    @ResponseStatus(code= HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalArgumentException(IllegalArgumentException e) {
        return new ErrorResult("bad", e.getMessage());
    }

}

api의 예외처리부분을 별도의 클래스로 뺀 것이다. RestController 어노테이션이 붙은 클래스에 적용된다.

 

@ControllerAdvice(annotations = Controller.class)
public class ExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgumentException(IllegalArgumentException ex) {
        return "error/400";
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public String handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
        return "error/missingRequestParameter";
    }
}

html 예외처리부분을 별도의 클래스로 뺀 것이다. Controller 어노테이션이 붙은 클래스에 적용된다.

 

@ControllerAdvice 와 @RestControllerAdvice는 범위를 지정할 수 있는다. 어노테이션, 패키지, 클래스 를 지정할 수 있다.

@ControllerAdvice(annotations = Controller.class) => 어노테이션 지정

@ControllerAdvice("org.example.controller") => 패키지 지정

@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) => 클래스 지정

'Java' 카테고리의 다른 글

spring 검증(validation, bean validation)  (0) 2025.04.11
spring 로그인 세션과 인터셉터  (0) 2025.04.03
spring 단위 테스트 코드 작성  (0) 2025.03.11
spring 기본 개념  (0) 2025.03.11
thymeleaf 템플릿 엔진  (0) 2025.02.11