본문 바로가기
Spring

Spring의 HttpMediaTypeNotAcceptableException 예외 해결

by 코더 제이콥 2023. 7. 4.

1. 개요

스프링으로 프로젝트를 진행하다가 만난 상황입니다. 회원가입 시 이메일이 중복되었는지 확인하기 위해 다음과 같이 코드를 작성했습니다.

$.ajax({
    type: "post",
    url: "/register/validate/email",
    data: {
        email: email
    },
    success: function (result){
        console.log(result);
    },
    error: function (result){
        console.log(result);
        $('.email__result').html(response.message);
        isValidatedEmail = false;
    }
});

ajax를 통해 유효성 검사하는 코드입니다.

@PostMapping("/register/validate/email")
public ResponseEntity<Object> validateEmail(@RequestParam String email){
    memberService.validatePresentEmail(email);

    BaseResponse response = new BaseResponse(HttpStatus.OK, "사용 가능한 이메일입니다.", true);
    return ResponseEntity
            .status(HttpStatus.OK)
            .body(response);
}

@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {
    public void validatePresentEmail(String email) {
        Optional<Member> findMember = memberRepository.findByEmail(email);

        if(findMember.isPresent()){
            throw new MemberPresentException("이미 가입된 이메일입니다.");
        }
    }
}

컨트롤러에서는 email이 사용가능한 지 확인한 후 응답합니다.

@ExceptionHandler(PresentException.class)
public ResponseEntity<Object> handlePresentException(Exception e){
    log.info("예외 발생 - PresentException : {}", e.getMessage());

    BaseResponse response = new BaseResponse(HttpStatus.CONFLICT, e.getMessage(), false);
    return ResponseEntity
            .status(HttpStatus.CONFLICT)
            .body(response);
}

ControllerAdvice 예외처리입니다. 코드만 보면 문제가 없었는데, Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation] 예외가 발생합니다.

2. 원인 분석

그런데 이상했습니다. HttpMediaTypeNotAcceptableException은 클라이언트의 Accept 헤더와 서버의 응답 형식이 일치하지 않을 때 발생합니다. 하지만 저는 컨트롤러의 클래스 레벨에 @RestController 어노테이션을 삽입했습니다. 따라서 모든 메소드는 @ResponseBody 어노테이션이 붙을 것입니다.

@ResponseBody가 붙은 메소드는 뷰 템플릿을 응답하는 것이 아니라 json으로 응답이 갑니다. 또 응답을 받는 ajax 코드에서 header의 accept 설정을 하지 않으면 기본 값으로 */*가 설정되는데 이는 모든 미디어 타입을 허용한다는 뜻입니다.

디버깅으로 찾아봐도, 도저히 모르겠어서 인터넷 검색을 해봤는데 다음과 같은 결과를 얻었습니다.

"Could not find acceptable representation" using spring-boot-starter-web
https://stackoverflow.com/questions/28466207/could-not-find-acceptable-representation-using-spring-boot-starter-web

저는 컨트롤러에서 ResponseEntity로 응답을 내릴 때 body 메소드에 BaseResponse란 객체를 만들어서, 생성자로 값을 초기화한 이후 해당 객체를 담았습니다. HttpMediaTypeNotAcceptableException이 발생한 이유는 이 객체에 getter를 만들지 않아 생긴 예외였습니다.

@RequiredArgsConstructor
public class BaseResponse {
    private final HttpStatus status;
    private final String message;
    private final boolean isSuccess;
}

하... 진짜 이럴 때 정말 짜증나면서도 예외를 해결해서 기쁘달까요. 복잡미묘합니다. 위의 코드에서 getter를 추가하면 성공적으로 로직이 작동합니다. 아무튼 저 위의 스택 오버플로우 링크를 가보시면 최고로 많이 좋아요를 받은 답변에 이런 내용이 있었습니다.

 ... and in your example will result in [].

대충 결과가 []를 받을 것이라는 뜻 같습니다. 그냥 블로그 포스팅을 끝내면 심심하니까 디버깅 모드로 한번 확인해보겠습니다.

보시면 141번 라인에 returnValue에서 200 OK는 정상적으로 찍힙니다. 이는 제가 컨트롤러에서 내려준 응답과 같습니다. 그리고 다음에 BaseResponse 객체의 값이 []인 것을 확인할 수 있었습니다.

3. 결론

ResponseEntity body 메소드의 파라미터로 객체를 넣은 후 반환하면, 해당 반환 객체에 getter가 있는지 확인합시다.