Spring REST API 예외처리 코드 작성법

  1. error response body에 담길 class 작성
  2. custom exception class 작성
  3. global exception handler 작성
  4. controller에서 예외처리

1. error response body에 담길 class 작성

선택 사항이긴 하지만, server에서 client에 error response를 보낼때 좀 더 가독성이 좋고 클라이언트 친화적으로 코딩하기 위해 작성한다. 다음의 예시 코드를 참고하자.

public class StudentErrorResponse {

    private int status;
    private String message;
    private long timeStamp;

    public StudentErrorResponse() {

    }

    public StudentErrorResponse(int status, String message, long timeStamp) {
        this.status = status;
        this.message = message;
        this.timeStamp = timeStamp;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public long getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(long timeStamp) {
        this.timeStamp = timeStamp;
    }
}

간단히 status code, error message, error time stamp를 필드값으로 가지고 있는 class이다.

2. custom exception class 작성

이 단계도 그냥 기본 제공되는 exception class를 사용해도 되지만, 커스텀이 필요한 경우도 있기 때문에 작성하는 연습을 해보기 위해 작성한다. 다음의 예시 코드를 참고하자.

public class StudentNotFoundException extends RuntimeException {

    public StudentNotFoundException(String message) {
        super(message);
    }

    public StudentNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public StudentNotFoundException(Throwable cause) {
        super(cause);
    }
}

3. global exception handler 작성

다음은 @ControllerAdvice를 사용한 global exception handler를 작성해보자. @ControllerAdvice를 이용하면 애플리케이션 내 모든 컨트롤러에서 발생하는 예외를 처리할 수 있기 때문에, 코드의 중복을 방지할 수 있다. 다음의 예시 코드를 참고하자.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class StudentRestExceptionHandler {

    @ExceptionHandler
    public ResponseEntity<StudentErrorResponse> handleException(StudentNotFoundException exc) {

        // create a StudentErrorResponse
        StudentErrorResponse error = new StudentErrorResponse();

        error.setStatus(HttpStatus.NOT_FOUND.value());
        error.setMessage(exc.getMessage());
        error.setTimeStamp(System.currentTimeMillis());

        // return ResponseEntity
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler
    public ResponseEntity<StudentErrorResponse> handleException(Exception exc) {

        // create a StudentErrorResponse
        StudentErrorResponse error = new StudentErrorResponse();

        error.setStatus(HttpStatus.BAD_REQUEST.value());
        error.setMessage(exc.getMessage());
        error.setTimeStamp(System.currentTimeMillis());

        // return ResponseEntity
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }
}

이 예시 코드에선 400 Bad Request, 404 Not Found 에러를 다루었다.

4. controller에서 예외처리

지금까지 코드를 잘 작성 하였다면, 엔드포인트에서 실제 예외를 처리하는 로직을 작성해야 한다. 다음의 간단한 예제 코드를 참고해보자.

@GetMapping("/students/{studentId}")
public Student getStudent(@PathVariable int studentId) {

    // just index into the list ... keep it simple for now
    // check the studentId again list size

    if ( (studentId >= theStudents.size()) || (studentId < 0)) {
        throw new StudentNotFoundException("Student id not found - " + studentId);
    }

    return theStudents.get(studentId);
}

위 예제 코드는 학생 리스트의 사이즈보다 큰 값을 요청받으면 StudentNotFoundException 에러를 터트리게 되고 이 에러가 발생하면 아까 3번에서 작성한 StudentRestExceptionHandler의 exception handler가 동작하고, 404 Not Found error를 response body와 함께 보내게 된다. 이제 코드 작성은 끝났으니 API테스트를 해보자.

API Test

API Test는 Talend API Tester를 사용한다. 이는 크롬 브라우저에서 사용할 수 있는 확장 프로그램으로, 무료이면서 빠르고 간편해 개발하며 API 테스트를 할 때 아주 유용하게 사용하고 있다.

API 명세

GET /api/students/{studentId} HTTP/1.1
Host: http://localhost:8080

1. 정상 API 호출 (200 OK)

GET /api/students/0 HTTP/1.1
Host: http://localhost:8080

image

2.1 비정상 API 호출 (404 Not Found)

GET /api/students/999 HTTP/1.1
Host: http://localhost:8080

image

2.2 비정상 API 호출 (400 Bad Request)

GET /api/students/abc HTTP/1.1
Host: http://localhost:8080

image

마치며

이번 시간에는 Spring 프로젝트에서 REST API 예외처리를 하는 법에 대해서 알아보았다. 예외처리를 하나 해주기 위해 작성해야 하는 코드가 적지 않다는 것을 느꼈지만, 그럼에도 클라이언트 친화적인 코드를 작성하기 위해선 최대한 많은 예외처리를 해줘야 할 것 같다. 그리고 4XX 에러와 5XX 에러는 매우 많은 차이가 있다. 쉽게 말하면 4XX 에러는 클라이언트가 잘못을 한 경우이고, 5XX 에러는 서버에서 잘못을 한 경우이니, 예외코드를 작성할 때 혼돈을 방지하기 위해 제대로 된 에러 메세지와 status code를 보내줘야 한다. 클라이언트 요청이 잘못 되었음에도 불구하고 5XX에러를 뱉어버리면 클라이언트는 요청을 제대로 했는데 지금 서버에 뭔가 문제가 생겼구나 라고 생각할 수 있기 때문이다.

댓글남기기