본문 바로가기

항해 99/Spring

ExceptionHandler

예외 처리는 애플리케이션을 만드는데 매우 중요한 부분을 차지함, Spring 프레임워크는 다양한 에러 처리 방법을 제공함

Spring의 기본적인 예외 처리

@RestController
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;
  
    @GetMapping("/product/{id}")
    public Response getProduct(@PathVariable String id){
        return productService.getProduct(id);
    }
}

 

위 컨트롤러의 getProduct에서 NoSuchElementFoundException 예외가 발생했다면 접속한 환경에 따라 다른 에러 처리를 받게 된다.

 

Spring은 에러 처리를 위한 BasicErrorController를 구현해두었고, 스프링 부트는 예외가 발생하면 기본적으로 /error로 에러 요청을 다시 전달하도록 WAS 설정을 해둠, 그래서 별도의 설정이 없다면 예외 발생 시에 BasicErrorController로 에러 처리 요청이 전달됨(스프링 부트의 WebMvcAutoConfiguration를 통해 자동 설정이 되는 WAS의 설정).

 

기본 에러처리 방식 흐름

WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러
-> 컨트롤러(예외발생) -> 인터셉터 -> 서블릿(디스패처 서블릿) -> 필터 -> WAS(톰캣)
-> WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러(BasicErrorController)
  • 기본적인 에러 처리 방식은 결국 에러 컨트롤러를 한번 더 호출하는 것
    • 필터나 인터셉터가 다시 호출될 수 있는데, 이를 제어하기 위해서는 별도의 설정이 필요
  • 서블릿은 dispatcherType로 요청의 종류를 구분하는데, 일반적인 요청은 REQUEST이며 에러 처리 요청은 ERROR 
  • 필터는 서블릿 기술이므로 필터 등록(FilterRegistrationBean) 시에 호출될 dispatcherType 타입을 설정할 수 있고, 별도의 설정이 없다면 REQUEST일 경우에만 필터가 호출됨
  • 인터셉터는 스프링 기술이므로 dispatcherType을 설정할 수 없어 URI 패턴으로 처리가 필요

 

BasicErrorCntroller의 동작 및 에러 속성

basicErrorController는 accept 헤더에 따라 에러 페이지를 반환하거나 에러 메시지를 반환(경로는 /error로 정의, properties에서 server.error.path로 변경 가능)

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    private final ErrorProperties errorProperties;
    ...

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        ...
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        ...
        return new ResponseEntity<>(body, status);
    }
    
    ...
}

 

errorHtml()과 error()는 모두 getErrorAttributeOptions를 호출해 반환할 에러 속성을 얻는데, 기본적으로 DefaultErrorAttributes로부터 반환할 정보를 가져옴, DefaultErrorAttributes는 전체 항목들에서 설정에 맞게 불필요한 속성들을 제거함

  • timestamp : 에러가 발생한 시간
  • status : 에러의 Http 상태
  • error : 에러 코드
  • path : 에러가 발생한 uri
  • exception: 최상위 예외 클래스의 이름(설정 필요)
  • message: 에러에 대한 내용(설정 필요)
  • errors: BindingExecption에 의해 생긴 에러 목록(설정 필요)
  • trace: 에러 스택 트레이스(설정 필요)
{
    "timestamp": "2021-12-31T03:35:44.675+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/product/5000"
}
  • 기본 설정으로 받는 에러 응답은 클라이언트 입장에서 유용하지 못함
    • properties를 통해 에러 응답을 노출할 수 있지만 유의미한 에러 응답을 전달하기 어려움
    • 운영환경에서 구현이 노출되는 trace는 제공하지 않는 게 좋음

properties 예시

server.error.include-message: always
server.error.include-binding-errors: always
server.error.include-stacktrace: always
server.error.include-exception: false

 

 

 

스프링이 제공하는 다양한 예외처리 방법

자바에서는 예외 처리를 위해 try-catch를 사용해야 하지만 try-catch를 모든 코드에 붙이는 것은 비효율적임.

Spring은 에러 처리라는 공통 관심사(cross-cutting concerns)를 메인 로직으로부터 분리하는 다양한 예외 처리 방식을 추상화한 HandlerExceptionResolver 인터페이스를 만듦.

 

대부분의 HandlerExceptionResolver는 발생한 Exception을 catch하고 HTTP 상태나 응답 메시지 등을 설정한다. 그래서 WAS 입장에서는 해당 요청이 정상적인 응답인 것으로 인식하고 복잡한 WAS의 에러 전달이 진행되지 않음.

 

HandlerExceptionResolver

public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request, 
            HttpServletResponse response, Object handler, Exception ex);
}

 

Object 타입인 handler는 예외가 발생한 컨트롤러 객체임, 예외가 던져지면 디스패처 서블릿까지 전달되고, 적절한 예외처리를 위해 HandlerExceptionResolver 구현체들을 빈으로 등록해서 관리함, 그리고 적용 가능한 구현체를 찾아 예외처리를 하는데 우선순위대로 4가지 구현체들이 빈으로 등록되어 있음

  • DefaultErrorAttributes : 에러 속성을 저장하며 직접 예외를 처리하지는 않는다.
  • ExceptionHandlerExceptionResolver : 에러 응답을 위한 Controller나 ControllerAdvice에 있는 ExceptionHandler를 처리함
  • ResponseStatusExceptionResolver : Http 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException를 처리함
  • DefaultHandlerExceptionResolver :  스프링 내부의 기본 예외들을 처리함

DefaultErrorAttributes는 직접 예외를 처리하지 않고 속성만 관리하므로 성격이 다름, 내부적으로는 DefaultErrorAttributes를 제외하고 직접 예외를 처리하는 3가지 ExceptionResolver들을 HandlerExceptionResolverComposite로 모아서 관리함(컴포지트 패턴을 적용해 실제 예외 처리기들을 따로 관리하는 것)

 

ExceptionResolver를 동작시켜 에러를 처리하는 도구

  1. ResponseStatus
  2. ResponseStatusException
  3. ExceptionHandler
  4. ControllerAdvice, RestControllerAdvice

 

@ResponseStatus

에러 HTTP 상태를 변경하도록 도와주는 어노테이션

 

적용

  • Exception 클래스 자체
  • 메서드에 @ExceptionHandler와 함께
  • 클래스에 @RestControllerAdvice와 함께

커스텀 예외 클래스에 @ResponseStatus로 응답 상태를 지정할 수 있음

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NoSuchElementFoundException extends RuntimeException {
  ...
}
  • 설정 시 ResponseStatusExceptionResolver가 지정해준 상태로 에러 응답이 내려가도록 처리함
  • BasicErrorController에 의한 응답으로 복잡한 WAS의 에러 요청 전달이 진행 되는 것임
    • @ResponseStatus를 처리하는 ResponseStatusExceptionResolver가 WAS까지 예외를 전달함

한계점

  • 에러 응답의 내용(Payload)를 수정할 수 없음(DefaultErrorAttributes를 수정하면 가능함)
  • 예외 클래스와 강하게 결합되어 같은 예외는 같은 상태와 에러 메시지를 반환함
  • 별도의 응답 상태가 필요할 경우 예외 클래스를 추가해야 함
  • WAS까지 예외가 전달되고, WAS의 에러 요청 전달이 진행됨

 

 

@ResponseStatusException

외부 라이브러리 정의 코드는 일반적으로 수정할 수 없으므로 @ResponseStatus를 붙일 수 없음

@ResponseStatus의 프로그래밍적 대안으로 손쉽게 에러를 반환할 수 있음.

ResponseStatusException은 HttpStatus와 함께 선택적으로 reason과 cause를 추가할 수 있고, UnCheck 예외를 상속받아 명시적으로 에러 처리를 하지 않아도 됨

 

사용 예시

@GetMapping("/product/{id}")
public ResponseEntity<Product> getProduct(@PathVariable String id) {
    try {
        return ResponseEntity.ok(productService.getProduct(id));
    } catch (NoSuchElementFoundException e) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Item Not Found");
    }
}
  • 예외 발생 시 ResponseStatusExceptionResolver가 에러를 처리함

이점

  • 기본적인 예외처리를 빠르게 적용할 수 있으며 손 쉽게 프로토타이핑할 수 있음
  • HttpStatus를 직접 설정하여 예외 클래스와의 결합도를 낮출 수 있음
  • 불필요하게 많은 별도의 예외 클래스를 만들지 않아도 됨
  • 프로그래밍 방식으로 예외를 직접 생성하므로 예외를 더욱 잘 제어할 수 있음

한계점

  • 직접 예외 처리를 프로그래밍하므로 일관된 예외 처리가 어려움
  • 예외 처리 코드가 중복될 수 있음
  • Spring 내부의 예외를 처리하는 것이 어려움
  • 예외가 WAS까지 전달되고, WAS의 에러 요청 전달이 진행됨

 

 

@ExceptionHandler

매우 유연하게 에러를 처리할 수 있는 방법을 제공하는 기능으로 아래에 어노테이션을 추가함으로써 에러를 손 쉽게 처리할 수 있음

  • Controller 메서드
  • @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메서드

사용 예시

@RestController
@RequiredArgsConstructor
public class ProductController {

  private final ProductService productService;
  
  @GetMapping("/product/{id}")
  public Response getProduct(@PathVariable String id){
    return productService.getProduct(id);
  }

  @ExceptionHandler(NoSuchElementFoundException.class)
  public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException exception) {
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
  }
}

 

  • @ExceptionHandler는 Exception 클래스들을 속성으로 받아 처리할 예외를 지정할 수 있음, 예외 클래스를 지정하지 않을 경우, 파라미터에 설정된 에러 클래스를 처리함
  • @ResponseStatus와도 결합이 가능, ResponseEntity에서도 Status를 지정하고 @ResponseStatus도 있을 경우 ResponseEntity가 우선순위를 가짐
  • @ResponseStatus와 달리 에러 응답(Payload)을 자유롭게 다룰 수 있다는 점에서 유연함
    • code: 어떠한 종류의 에러가 발생하는지에 대한 에러 코드
    • message: 왜 에러가 발생했는지에 대한 설명
    • errors: 어느 값이 잘못되어 @Valid에 의한 검증이 실해한 것인지를 위한 에러 목록

code의 값으로는 BAD_REQUEST와 같은 Http 표준 상태와 같이 가독성이 좋은 값을 사용하는 것이 좋음.

@RestController
@RequiredArgsConstructor
public class ProductController {

    ...

    @ExceptionHandler(NoSuchElementFoundException.class)
    public ResponseEntity<ErrorResponse> handleItemNotFoundException(NoSuchElementFoundException exception) {
        ...
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
        ...
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAllUncaughtException(Exception exception) {
        ...
    }
}

 

Spring은 예외가 발생하면 가장 구체적인 예외 핸들러를 먼저 찾고, 없으면 부모 예외의 핸들러를 찾음

  • NullPointerException이 발생했다면, 위에서는 NullPointerException 처리기가 없으므로 Exception에 대한 처리기가 찾아짐

사용시 주의할 점

@ExceptionHandler에 등록된 예외 클래스와 파라미터로 받는 예와 클래스가 동일해야 함, 다를 경우 런타임 시점에서 에러가 발생함

 

ExceptionHandler의 파라미터로 HttpServletRequest나 WebRequest 등을 얻을 수 있으며 반환 타입으로는 ResponseEntity, String, void 등 자유롭게 활용할 수 있음(공식문서)

 

@ExceptionHandler는 컨트롤러에 구현하므로 특정 컨트롤러에서만 발생하는 예외만 처리 됨, 컨트롤러에 에러 처리 코드가 섞여 에러 처리 코드가 중복될 가능성이 높음.

 

 

 

@ControllerAdvice, @RestControllerAdvice

전역적으로 @ExceptionHandler를 적용할 수 있는 어노테이션, 두 개의 차이점은 Controller와 RestController와 같이 @ResponseBody가 붙어 있어 응답을 Json으로 내려줌

 

구조

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
    ...
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    ...
}
  • ControllerAdvice는 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를 적용해 줌, @ControllerAdvice에는 @Component 어노테이션이 있어 선언된 클래스는 스프링 빈으로 등록 됨.

사용 예시

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NoSuchElementFoundException.class)
    protected ResponseEntity<?> handleNoSuchElementFoundException(NoSuchElementFoundException e) {
        final ErrorResponse errorResponse = ErrorResponse.builder()
                .code("Item Not Found")
                .message(e.getMessage()).build();

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }
}
  • ControllerAdvice는 전역적으로 적용되는데, 만약 특정 클래스에만 제한적으로 적용하고 싶다면 @RestControllerAdvice의 basePackages 등을 설정함으로써 제한할 수 있음
    • basePackages: 탐색 패키지 지정, org.example.controllers 패키지, 하위 패키지까지 모두 탐색
    • basePackagesClasses: 탐색 클래스 지정, 클래스의 맨 위에 있는 package부터 시작
    • assignableTypes 속성을 이용하면 클래스 단위로도 ControllerAdvice를 적용할 수 있음(basePackages 속성보다 조금 더 세밀하게 처리를 분리해 주고 싶을 때 사용하면 유용)

 

Spring은 스프링 예외를 미리 처리해 둔 ResponseEntityExceptionHandler를 추상 클래스로 제공함, 스프링 예외에 대한 ExceptinHandler가 모두 구현되어 있어 ControllerAdvice 클래스가 이를 상속받게 하면 됨.

  • 이 추상 클래스를 상속받지 않을 경우 예외들은 DefaultHandlerExceptionResolver가 처리하게 되어 클라이언트가 일관된 에러 응답을 받지 못함
  • 기본적으로 에러 메시지를 반환하지 않으므로, 스프링 예외에 대한 에러 응답을 보내기 위해서는 메서드 오버라이딩이 필요함
public abstract class ResponseEntityExceptionHandler {
    ...

    protected ResponseEntity<Object> handleExceptionInternal(
        Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
            
        ...
    }
}

 

ControllerAdvice 이점

  • 하나의 클래스로 모든 컨트롤러에 대해 전역적으로 예외 처리가 가능함
  • 직접 정의한 에러 응답을 일관성 있게 클라이언트에게 내려줄 수 있음
  • 별도의 try-catch문이 없어 코드의 가독성이 높아짐

사용 시 주의사항

  • 한 프로젝트 당 하나의 ControllerAdvice만 관리하는 것이 좋음
  • 여러 ControllerAdvice가 필요할 시 basePackages나 annotations 등을 지정해야 함
  • 직접 구현한 Exception 클래스들은 한 공간에서 관리한다

 

 

Spring 예외 처리 흐름

예외 처리기들은 스프링의 빈으로 등록되어 있고, 예외가 발생하면 순차적으로 다음의 Resolver 들이 처리 가능한지 판별한 후 예외가 처리 됨

  1. ExceptionHandlerExceptionResolver : 에러 응답을 위한 Controller나 ControllerAdvice에 있는 ExceptionHandler를 처리함
  2. ResponseStatusExceptionResolver : Http 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException를 처리함
  3. DefaultHandlerExceptionResolver :  스프링 내부의 기본 예외들을 처리한다.

 

 

  1. ExceptionHandlerExceptionResolver가 동작함
    1. 예외가 발생한 컨트롤러 안에 적합한 @ExceptionHandler가 있는지 검사함
    2. 컨트롤러의 @ExceptionHandler에서 처리가능하다면 처리하고, 그렇지 않으면 ControllerAdvice로 넘어감
    3. ControllerAdvice안에 적합한 @ExceptionHandler가 있는지 검사하고 없으면 다음 처리기로 넘어감
  2. ResponseStatusExceptionResolver가 동작함
    1. @ResponseStatus가 있는지 또는 ResponseStatusException인지 검사함
    2. 맞으면 ServletResponse의 sendError()로 예외를 서블릿까지 전달되고, 서블릿이 BasicErrorController로 요청을 전달함
  3. DefaultHandlerExceptionResolver가 동작함
    1. Spring의 내부 예외인지 검사하여 맞으면 에러를 처리하고 아니면 넘어감
  4. 적합한 ExceptionResolver가 없으므로 예외가 서블릿까지 전달되고, 서블릿은 SpringBoot가 진행한 자동 설정에 맞게 BasicErrorController로 요청을 다시 전달함

 ExceptionHandler나 ControllerAdvice처럼 직접 에러를 반환하는 경우에는 BasicErrorController를 거치지 않지만 @ResponseStatus, ResponseStatusException 등과 같이 직접 에러 응답을 반환하지 않는 경우에는 최종적으로 BasicErrorController를 거쳐 에러가 처리 됨(내부에서는 컨트롤러로 요청이 전달되는 과정이 2번 진행 됨)

 

 

 

예외 처리 사용 예시

1. Error 관련 properties 설정

  • 필요한 경우에만 설정
# application.yml
server:
  error:
    include-exception: false      # Response 에 Exception 을 표시할지
    include-message: always       # Response 에 Exception Message 를 표시할지 (never | always | on_param)
    include-stacktrace: on_param  # Response 에 Stack Trace 를 표시할지 (never | always | on_param) on_trace_params 은 deprecated
    whitelabel.enabled: true      # 에러 발생 시 Spring 기본 에러 페이지 노출 여부

 

2. Error Code

@Getter
@AllArgsConstructor
public enum ErrorCode {

    /* 400 BAD_REQUEST : 잘못된 요청 */
    INVALID_REFRESH_TOKEN(BAD_REQUEST, "리프레시 토큰이 유효하지 않습니다"),
    MISMATCH_REFRESH_TOKEN(BAD_REQUEST, "리프레시 토큰의 유저 정보가 일치하지 않습니다"),
    CANNOT_FOLLOW_MYSELF(BAD_REQUEST, "자기 자신은 팔로우 할 수 없습니다"),

    /* 401 UNAUTHORIZED : 인증되지 않은 사용자 */
    INVALID_AUTH_TOKEN(UNAUTHORIZED, "권한 정보가 없는 토큰입니다"),
    UNAUTHORIZED_MEMBER(UNAUTHORIZED, "현재 내 계정 정보가 존재하지 않습니다"),

    /* 404 NOT_FOUND : Resource 를 찾을 수 없음 */
    MEMBER_NOT_FOUND(NOT_FOUND, "해당 유저 정보를 찾을 수 없습니다"),
    REFRESH_TOKEN_NOT_FOUND(NOT_FOUND, "로그아웃 된 사용자입니다"),
    NOT_FOLLOW(NOT_FOUND, "팔로우 중이지 않습니다"),

    /* 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재 */
    DUPLICATE_RESOURCE(CONFLICT, "데이터가 이미 존재합니다"),

    ;

    private final HttpStatus httpStatus;
    private final String detail;
}
  • 에러 형식을 Enum 클래스로 정의
  • 응답으로 내보낼 HttpStatus와 에러 메시지로 사요알 String을 가짐
  • ResponseStatusException과 비슷하나 가장 큰 차이는 개발자가 정의한 새로운 Exception을 모두 한 곳에서 관리하고재사용할 수 있는 점

3. CustomException

@Getter
@AllArgsConstructor // 사용 시 주의
public class CustomException extends RuntimeException {
    private final ErrorCode errorCode;
}
  • 전역으로 사용할 CustomException
  • RuntimeException을 상속받아 Unchecked Exception으로 활용
  • 생성자로 ErroCode를 받음

4. ErrorResponse

@Getter
@Builder
public class ErrorResponse {
    private final LocalDateTime timestamp = LocalDateTime.now();
    private final int status;
    private final String error;
    private final String code;
    private final String message;

    public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode) {
        return ResponseEntity
                .status(errorCode.getHttpStatus())
                .body(ErrorResponse.builder()
                        .status(errorCode.getHttpStatus().value())
                        .error(errorCode.getHttpStatus().name())
                        .code(errorCode.name())
                        .message(errorCode.getDetail())
                        .build()
                );
    }
}
  • 실제로 유저에게 보낼 응답 Format
  • status, code 값은 없어도 됨
  • ErrorCode를 받아서 ResponseEntity<ErrorResponse>로 변환해줌

5. @ControllerAdvice , @Exception

@ControllerAdvice는 프로젝트 전역에서 발생하는 모든 예외를 잡아줌

@ExceptionHandler는 발생한 특정 예외를 잡아서 하나의 메서드에서 공통 처리할 수 있게 해줌

같이 사용하면 모든 예외를 잡은 후 Exception 종류 별로 메서드를 공통 처리할 수 있음

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = { ConstraintViolationException.class, DataIntegrityViolationException.class})
    protected ResponseEntity<ErrorResponse> handleDataException() {
        log.error("handleDataException throw Exception : {}", DUPLICATE_RESOURCE);
        return ErrorResponse.toResponseEntity(DUPLICATE_RESOURCE);
    }

    @ExceptionHandler(value = { CustomException.class })
    protected ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
        log.error("handleCustomException throw CustomException : {}", e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }
}
  • View를 사용하지 않고 RestAPI로만 사용할 때 쓸 수 있는 @RestControllerAdvice를 사용함
  • handlerDataException 메서드에서는 hibernate 관련 에러를 처리
  • handlerCustomException 메서드는 직접 정의한 CustomException을 사용
  • Exception 발생 시 넘겨받은 ErrorCode를 사용해서 사용자에게 보여주는 에러 메시지를 정의

6. Exception 사용

@RequiredArgsConstructor
@Service
public class MemberService {
    private final MemberRepository memberRepository;

    @Transactional
    public boolean follow(Long memberId) {
        Member currentMember = getCurrentMember();

        // 팔로우할 상대방 정보가 없는 경우
        Member targetMember = memberRepository.findById(memberId)
                .orElseThrow(() -> new CustomException(MEMBER_NOT_FOUND));

        // 자기 자신을 팔로우 하려는 경우
        if (currentMember.equals(targetMember))  {
            throw new CustomException(CANNOT_FOLLOW_MYSELF);
        }

                // code...
    }
}
  • 토이 프로젝트 일부 코드
  • 상대방의 Member ID를 입력 받아 팔로우하는 기능
  • Exception에 담겨지는 ErrorCode만 보고도 어떤 종류의 문제가 발생한 건지 알 수 있음
  • 또한 불필요하게 여러 Exception을 만들지 않고 ErrorCode만 새로 추가하면 사용 가능

 MEMBER_NOT_FOUND 실제 응답

{
  "timestamp": "2021-03-14T03:29:01.878659",
  "status": 404,
  "error": "NOT_FOUND",
  "code": "MEMBER_NOT_FOUND",
  "message": "해당 유저 정보를 찾을 수 없습니다"
}

 

CANNOT_FOLLOW_MYSELF 실제 응답

{
  "timestamp": "2021-03-14T03:16:25.98361",
  "status": 400,
  "error": "BAD_REQUEST",
  "code": "CANNOT_FOLLOW_MYSELF",
  "message": "자기 자신은 팔로우 할 수 없습니다"
}

 

 

'항해 99 > Spring' 카테고리의 다른 글

Jwt Access Token과 Refresh Token  (0) 2024.03.14
REST API URI 규칙  (0) 2024.03.13
QueryDSL  (0) 2024.03.08
Entity와 DTO의 분리  (0) 2024.03.08
lombok 주의 사항  (0) 2024.03.06