// Developer · Debugging Reports

뉴스도 디버깅이 필요해

마주친 오류를 재현하고, 원인을 추적하고, 기록합니다. Java · Spring · DB · 실무 트러블슈팅.

debug — today.log
$ debug --trace "오늘의 뉴스"
> 헤드라인 파싱 중...
> 원인 추적: 근거 누락 · 출처 불명
> 패치 적용: 사실 확인 + 출처 보강
✓ RESOLVED
TOTAL
TODAY
YESTERDAY

Categories

웹개발/Spring

@Controller와 @RestController 차이 JSP와 JSON으로 정리

자바를잡아2026. 7. 1. 08:00
반응형

두 어노테이션이 헷갈리는 지점

Spring 컨트롤러를 작성하다 보면 @Controller@RestController 중 무엇을 붙여야 할지 망설이게 된다. 둘 다 요청을 받는 컨트롤러인데, 반환값을 다루는 방식이 다르다. @Controller @RestController 차이를 한 줄로 요약하면 이렇다. @Controller는 기본적으로 화면(뷰, 보통 JSP)을 돌려주고, @RestController는 데이터(주로 JSON)를 돌려준다.

그래서 증상도 갈린다. @RestController를 썼는데 JSP가 안 뜨고 "member/list" 같은 문자열이 화면에 그대로 찍히거나, @Controller에서 JSON을 기대했는데 뷰를 찾다가 404가 나는 식이다. 원인을 알려면 두 어노테이션이 반환값을 어떻게 해석하는지 보면 된다.

차이 비교표

구분 @Controller @RestController
기본 반환 의미 뷰 이름(화면) 응답 본문 데이터
반환값 "member/list" 뷰 리졸버가 JSP를 찾음 문자열 그 자체를 응답
@ResponseBody 메서드마다 직접 붙여야 데이터 응답 클래스 전체에 자동 적용
주 용도 JSP 등 화면 렌더링 REST API, JSON 응답
객체 반환 시 모델/뷰로 해석 JSON으로 변환
대표 상황 전통 웹(서버 렌더링) Ajax, 모바일 API, 프론트 분리

@Controller: 화면(JSP)을 돌려준다

1. 문자열 반환은 뷰 이름으로 해석된다

@Controller에서 메서드가 문자열을 반환하면, 그 문자열은 뷰 이름으로 해석된다. 뷰 리졸버(ViewResolver)가 접두어·접미어를 붙여 실제 JSP 경로를 찾는다.

@Controller
public class MemberController {

    @GetMapping("/member/list")
    public String list(Model model) {
        model.addAttribute("members", memberService.findAll());
        // "member/list" → /WEB-INF/views/member/list.jsp 로 해석 (설정에 따라)
        return "member/list";
    }
}

여기서 return "member/list"는 "member/list.jsp 화면을 보여줘라"는 뜻이다. 뷰 리졸버 설정(prefix/suffix)에 따라 실제 경로가 결정된다. 모델에 담은 데이터는 JSP에서 꺼내 쓴다. 전통적인 서버 사이드 렌더링 방식이고, 공공 SI의 화면 개발에서 가장 많이 보는 형태다.

2. @Controller에서 JSON을 내보내려면

같은 @Controller에서도 특정 메서드만 데이터를 응답하게 할 수 있다. 메서드에 @ResponseBody를 붙이면 된다.

@Controller
public class MemberController {

    @GetMapping("/api/members")
    @ResponseBody // 이 메서드는 뷰가 아니라 데이터로 응답
    public List<MemberVO> apiList() {
        return memberService.findAll(); // JSON으로 변환되어 응답
    }
}

@ResponseBody가 붙으면 반환값을 뷰 이름이 아니라 응답 본문으로 처리한다. 객체나 리스트면 메시지 컨버터가 JSON으로 바꿔 내보낸다. 즉 @Controller도 @ResponseBody만 붙이면 데이터 응답이 가능하다. 이 점이 다음 이야기로 이어진다.

@RestController: 데이터(JSON)를 돌려준다

1. @Controller + @ResponseBody의 합본

@RestController는 사실 @Controller에 @ResponseBody가 클래스 단위로 합쳐진 것이다. 그래서 클래스 안 모든 메서드가 기본적으로 데이터 응답이 된다. 메서드마다 @ResponseBody를 붙일 필요가 없다.

@RestController
public class MemberApiController {

    @GetMapping("/api/members")
    public List<MemberVO> list() {
        // 별도 어노테이션 없이 JSON으로 응답
        return memberService.findAll();
    }

    @GetMapping("/api/members/{id}")
    public MemberVO detail(@PathVariable String id) {
        return memberService.findById(id); // 객체 → JSON
    }
}

객체를 반환하면 JSON으로 변환되고, 문자열을 반환하면 그 문자열 자체가 응답 본문이 된다. REST API, Ajax 응답, 모바일 백엔드처럼 화면이 아니라 데이터를 주고받는 경우에 적합하다.

2. @RestController로 화면을 돌려주려 하면

여기서 흔한 실수가 나온다. @RestController에서 return "member/list"를 하면 JSP를 찾지 않고 "member/list"라는 문자열을 그대로 응답한다. 화면에 경로 문자열만 덜렁 찍히는 이유가 이것이다. 화면을 렌더링하려면 @Controller를 써야 한다. 둘을 용도에 맞게 나누는 게 핵심이다.

그래서 언제 무엇을 쓰나

  • JSP 같은 화면을 서버에서 렌더링해 보여 준다 → @Controller
  • JSON 데이터를 주고받는 REST API, Ajax 응답 → @RestController
  • 화면용 컨트롤러인데 일부 메서드만 데이터 응답이 필요하다 → @Controller + 해당 메서드에 @ResponseBody
  • 프로젝트가 화면과 API를 명확히 분리한다 → 컨트롤러 클래스 자체를 화면용/API용으로 나눠 관리

공공 SI에서는 JSP 기반 화면이 여전히 많아 @Controller가 기본이고, Ajax로 부분 데이터를 받는 곳에 @ResponseBody나 별도 @RestController를 섞어 쓰는 구조를 자주 본다. 프론트엔드를 분리한 신규 시스템일수록 @RestController 비중이 높다.

JSON이 안 나가거나 화면이 안 뜰 때

증상별로 원인을 좁힐 수 있다. @RestController인데 한글 JSON이 깨진다면 메시지 컨버터의 인코딩이나 produces 설정을 본다. 객체가 JSON으로 안 바뀌고 오류가 난다면 JSON 변환 라이브러리(Jackson 등)가 의존성에 있는지 확인한다. @Controller인데 화면을 못 찾고 404가 난다면 뷰 리졸버의 prefix/suffix와 실제 JSP 경로가 맞는지 본다. 반대로 @RestController에서 화면 경로 문자열이 그대로 찍힌다면, 화면용인데 어노테이션을 잘못 골랐다는 신호다.

// JSON 응답에 콘텐츠 타입·인코딩을 명시하고 싶을 때
@GetMapping(value = "/api/members", produces = "application/json; charset=UTF-8")
@ResponseBody
public List<MemberVO> list() {
    return memberService.findAll();
}

인코딩이나 메시지 컨버터 동작은 Spring 버전과 설정에 따라 다를 수 있으니, 환경에 맞춰 확인한다.

초급 개발자가 헷갈리는 부분

  • @RestController를 화면용으로 쓰고 JSP가 안 뜬다고 고민하는 경우. 화면은 @Controller다.
  • @RestController가 @Controller와 완전히 다른 것이라 여기는 경우. 사실은 @Controller + @ResponseBody다.
  • @Controller 메서드에서 객체를 반환하면 자동으로 JSON이 될 거라 기대하는 경우. @ResponseBody가 없으면 뷰 이름으로 해석된다.
  • 객체가 JSON으로 안 바뀌는데 원인을 컨트롤러에서만 찾는 경우. JSON 변환 라이브러리 의존성도 확인한다.

FAQ

Q1. @RestController와 @Controller는 뭐가 다른가요?

@Controller는 기본적으로 뷰(화면) 이름을 반환하고, @RestController는 데이터를 응답한다. @RestController는 @Controller에 @ResponseBody가 더해진 것이다.

Q2. @RestController를 썼는데 JSP가 안 뜹니다.

@RestController는 반환 문자열을 뷰가 아니라 응답 본문으로 처리한다. 화면을 렌더링하려면 @Controller를 써야 한다.

Q3. @Controller에서 JSON을 내보낼 수 있나요?

가능하다. 해당 메서드에 @ResponseBody를 붙이면 반환 객체가 JSON으로 변환되어 응답된다.

Q4. @ResponseBody는 정확히 무슨 역할인가요?

메서드 반환값을 뷰 이름이 아니라 HTTP 응답 본문으로 보내라는 표시다. 객체면 메시지 컨버터가 JSON 등으로 변환한다.

Q5. 객체를 반환했는데 JSON이 안 나옵니다.

@ResponseBody나 @RestController가 빠졌거나, JSON 변환 라이브러리(Jackson 등)가 의존성에 없을 수 있다. 둘 다 확인한다.

정리

@Controller @RestController 차이는 "화면을 돌려주느냐, 데이터를 돌려주느냐"로 갈린다. @Controller는 반환 문자열을 뷰 이름으로 해석해 JSP 같은 화면을 렌더링하고, @RestController는 반환값을 응답 본문으로 처리해 JSON 데이터를 내보낸다. 핵심은 @RestController가 곧 @Controller + @ResponseBody라는 점이다. 그래서 @Controller에서도 @ResponseBody만 붙이면 데이터 응답이 가능하다. 화면이 안 뜨거나 JSON이 안 나간다면 먼저 어노테이션 선택이 용도에 맞는지부터 확인하고, 뷰 리졸버 설정이나 JSON 변환 라이브러리를 점검하면 대부분 원인을 찾을 수 있다.

반응형