Spring API 응답 처리와 예외 관리: HTTP 상태코드 vs 커스텀 코드

민준·2025년 3월 22일

Spring Boot

목록 보기
1/3
post-thumbnail

Spring에서 컨트롤러(@Controller)를 통해 API 응답을 반환하는 방식과 예외 처리는 크게 두 가지 방식으로 나눌수 있다.


API는 결과의 성공/실패에 대한 정보를 2가지 방법으로 제공

API 응답을 처리할 때 성공/실패 여부를 전달하는 방법 두가지

  1. HTTP 상태코드(Status Code) 활용
  2. 커스텀 상태코드(Custom Status Code) 활용

1. HTTP 상태코드 활용

HTTP 상태코드는 클라이언트가 요청한 작업이 성공했는지, 실패했는지를 나타내는 표준 코드

HTTP 상태코드의 종류

  • 1XX (정보) → 요청을 받았으며, 처리 중
  • 2XX (성공) → 요청이 정상적으로 완료됨
  • 3XX (리다이렉션) → 다른 URL로 이동해야 함
  • 4XX (클라이언트 오류) → 사용자의 요청이 잘못됨
  • 5XX (서버 오류) → 서버에서 요청을 처리하는 중 문제 발생

자주 사용되는 HTTP 상태코드

상태코드의미설명
200 OK요청 성공정상적으로 요청을 처리하고 응답을 반환
201 Created리소스 생성 성공새로운 자원이 성공적으로 생성됨 (POST 요청 시)
400 Bad Request잘못된 요청클라이언트의 요청이 잘못됨 (유효성 검증 실패)
401 Unauthorized인증 실패로그인 정보 부족 등으로 접근 불가
403 Forbidden권한 없음인증은 했지만, 해당 작업을 수행할 권한이 없음
404 Not Found리소스 없음요청한 리소스를 찾을 수 없음
500 Internal Server Error서버 오류서버에서 예기치 못한 오류 발생

예제

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findUserById(id);
    if (user == null) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); // 404 응답
    }
    return ResponseEntity.ok(user); // 200 응답
}

커스텀 상태코드 활용

내부적으로 사용할 에러 코드가 필요할 경우, 자체적으로 정의한 상태 코드로 사용 가능

커스텀 상태코드 예시
| 상태코드 | 의미 | 설명 |
|---------|------|------|
| A01 | 타입 에러 | 요청한 데이터 타입이 맞지 않음 |
| A02 | 입력 에러 | 필수 입력 값이 누락됨 |
| B01 | 외부 서버 통신 오류 | 외부 API 호출 중 오류 발생 |

커스텀 상태코드의 특징

  • 내부 시스템에서 API 오류를 더 세밀하게 관리할 수 있음
  • 하지만, 모든 사람이 이해할 수 있도록 문서화가 필수적임
  • 추천 방식: 가능하면 HTTP 상태코드를 활용하고, 정말 필요한 경우에만 커스텀 상태코드 사용

예제

@GetMapping("/users/{id}")
public ResponseEntity<CustomResponse> getUser(@PathVariable Long id) {
    User user = userService.findUserById(id);
    if (user == null) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new CustomResponse("A02", "사용자를 찾을 수 없습니다."));
    }
    return ResponseEntity.ok(new CustomResponse("A00", "요청 성공", user));
}

ResponseEntity - HTTP Status Code 를 포함한 반환 클래스

ResponseEntity란?

  • ResponseEntity<T>는 Spring에서 제공하는 HTTP 응답을 감싸는 클래스
  • HTTP 상태코드 + 응답 데이터 + 헤더(Header) 를 함께 설정할 수 있음.

ResponseEntity<T> 사용하면 좋은 경우

  • API 호출 결과를 클라이언트에게 HTTP 상태코드와 함께 명확하게 전달할 때
  • 오류 발생 시, 적절한 상태코드를 지정해서 반환하고 싶을 때
  • API의 응답을 더 유연하게 조작하고 싶을 때

ResponseEntity 활용 예제

1. 단순 성공 응답 (200 OK)

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findUserById(id);
    return ResponseEntity.ok(user); // HTTP 200 OK
}

2. 새로운 리소스 생성 (201 Created)

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    User savedUser = userService.save(user);
    return ResponseEntity.status(HttpStatus.CREATED).body(savedUser); // HTTP 201 Created
}

3. 잘못된 요청 처리 (400 Bad Request)

@GetMapping("/users")
public ResponseEntity<String> getUser(@RequestParam(required = false) String name) {
    if (name == null || name.isEmpty()) {
        return ResponseEntity.badRequest().body("이름을 입력하세요!"); // HTTP 400 Bad Request
    }
    return ResponseEntity.ok("Hello, " + name);
}

4. 예외 발생 시 처리 (404 Not Found)

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    return userService.findUserById(id)
            .map(ResponseEntity::ok) // HTTP 200 OK
            .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).build()); // HTTP 404 Not Found
}

5. 서버 내부 오류 (500 Internal Server Error)

@GetMapping("/error")
public ResponseEntity<String> throwError() {
    throw new RuntimeException("서버 오류 발생!");
}

@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException e) {
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 오류: " + e.getMessage());
}

Optional 활용 - Null 예외 처리

Spring에서는 Optional을 활용하면 Null 처리와 예외 처리를 좀 더 쉽게 할 수 있음.

Optional 활용 예제

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
    return ResponseEntity.ok(user);
}

이렇게 하면 id에 해당하는 사용자가 없을 때, 자동으로 RuntimeException이 발생하며 500 Internal Server Error를 반환

Optional + orElseThrow 활용

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    return userRepository.findById(id)
            .map(ResponseEntity::ok) // 값이 있으면 200 OK 응답
            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다.")); // 404 응답
}

정리

  1. API 응답을 보낼 때는 HTTP 상태코드를 활용하는 것이 일반적
  2. ResponseEntity<T>를 활용하면, HTTP 상태코드를 포함하여 응답을 유연하게 반환할 수 있음
  3. 예외 발생 시 적절한 상태코드를 설정하는 것이 중요 (ex: 404 Not Found, 400 Bad Request)
  4. Optional.ofNullable().orElseThrow()를 사용하면, Null 예외 처리를 깔끔하게 할 수 있음

0개의 댓글