[Spring] 22. @ResponseBody와 @RestController
이전 글에서는 @RequestBody에 대해 살펴봤습니다. @RequestBody는 요청 Body에 들어있는 JSON 데이터를 Java 객체로 받을 때 사용하는 어노테이션입니다.
이번 글에서는 반대로 서버가 응답 데이터를 직접 반환할 때 사용하는 @ResponseBody와 @RestController에 대해 정리하겠습니다. Spring MVC에서는 Controller가 문자열을 반환할 때 그 문자열을 화면 이름으로 볼 수도 있고, 응답 데이터로 볼 수도 있습니다. 이 차이를 이해하는 것이 중요합니다.
핵심은 간단합니다. @ResponseBody는 Controller 메서드의 반환값을 View 이름으로 해석하지 않고, HTTP 응답 Body에 직접 담아 반환하게 해줍니다.
1. @ResponseBody란?
@ResponseBody는 Controller 메서드가 반환하는 값을 HTTP 응답 Body에 직접 담아 보내도록 하는 어노테이션입니다.
일반적인 @Controller에서는 문자열을 반환하면 View 이름으로 해석되는 경우가 많습니다.
@Controller
public class BoardController {
@GetMapping("/board/list")
public String list() {
return "board/list";
}
}
위 코드에서 return "board/list";는 문자열 데이터가 아니라 View 이름입니다. Spring은 board/list라는 화면을 찾으려고 합니다.
하지만 @ResponseBody를 붙이면 반환값을 화면 이름으로 보지 않고 응답 데이터로 보냅니다.
@Controller
public class BoardApiController {
@GetMapping("/api/hello")
@ResponseBody
public String hello() {
return "hello";
}
}
이 경우 hello라는 문자열이 View 이름이 아니라 응답 Body에 그대로 담겨 클라이언트로 전달됩니다.
GET /api/hello
→ hello 응답
2. View 반환과 Body 반환의 차이
Spring MVC에서 Controller 메서드가 문자열을 반환할 때는 상황에 따라 의미가 달라집니다.
| 구분 | View 반환 | Body 반환 |
| 사용 어노테이션 | @Controller |
@Controller + @ResponseBody |
| 반환값 의미 | 화면 이름 | 응답 데이터 |
| 예시 반환 | "board/list" |
"ok" |
| 주 사용 | JSP, Thymeleaf 화면 | API 응답, AJAX 응답 |
즉, 화면을 보여주고 싶다면 View 이름을 반환하고, 데이터를 바로 보내고 싶다면 @ResponseBody를 사용합니다.
return "board/list";
→ 화면 이름
@ResponseBody + return "ok";
→ 응답 데이터
3. 문자열 응답 예시
가장 간단한 예시로 문자열을 응답해보겠습니다.
@Controller
public class TestController {
@GetMapping("/api/status")
@ResponseBody
public String status() {
return "OK";
}
}
사용자가 /api/status로 요청하면 화면 파일을 찾는 것이 아니라 OK라는 문자열을 바로 응답합니다.
요청
GET /api/status
응답
OK
이런 방식은 간단한 상태 확인, 테스트 응답, AJAX 결과 응답에서 사용할 수 있습니다.
4. 객체를 JSON으로 반환하기
@ResponseBody는 문자열뿐만 아니라 객체도 반환할 수 있습니다. 객체를 반환하면 Spring이 JSON 형태로 변환해서 응답할 수 있습니다.
public class BoardDto {
private Long boardNo;
private String title;
private String content;
public BoardDto(Long boardNo, String title, String content) {
this.boardNo = boardNo;
this.title = title;
this.content = content;
}
public Long getBoardNo() {
return boardNo;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
@Controller
public class BoardApiController {
@GetMapping("/api/boards/1")
@ResponseBody
public BoardDto detail() {
return new BoardDto(1L, "Spring", "ResponseBody 정리");
}
}
이 요청의 응답은 다음과 같은 JSON 형태가 될 수 있습니다.
{
"boardNo": 1,
"title": "Spring",
"content": "ResponseBody 정리"
}
즉, Java 객체를 반환했지만 클라이언트는 JSON 데이터를 받게 됩니다.
5. List를 JSON 배열로 반환하기
여러 개의 데이터를 응답해야 한다면 List를 반환할 수도 있습니다.
@Controller
public class BoardApiController {
@GetMapping("/api/boards")
@ResponseBody
public List<BoardDto> list() {
return List.of(
new BoardDto(1L, "Spring MVC", "목록 응답"),
new BoardDto(2L, "Spring Boot", "API 응답")
);
}
}
응답은 JSON 배열 형태가 될 수 있습니다.
[
{
"boardNo": 1,
"title": "Spring MVC",
"content": "목록 응답"
},
{
"boardNo": 2,
"title": "Spring Boot",
"content": "API 응답"
}
]
이런 구조는 게시글 목록 API, 댓글 목록 API, 상품 목록 API에서 자주 사용됩니다.
6. Map을 응답하기
간단한 결과를 응답할 때는 Map을 사용할 수도 있습니다.
@Controller
public class MemberApiController {
@PostMapping("/api/login")
@ResponseBody
public Map<String, Object> login() {
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "로그인 성공");
return result;
}
}
응답은 다음과 같은 JSON 형태가 될 수 있습니다.
{
"success": true,
"message": "로그인 성공"
}
간단한 응답에는 Map도 사용할 수 있지만, 응답 구조가 정해져 있다면 응답 DTO를 따로 만드는 것이 더 관리하기 좋습니다.
7. @RestController란?
@RestController는 REST API를 만들 때 자주 사용하는 어노테이션입니다. @Controller와 @ResponseBody를 합친 것처럼 이해하면 쉽습니다.
@RestController
=
@Controller
+
@ResponseBody
즉, @RestController가 붙은 클래스의 메서드는 기본적으로 반환값을 View 이름이 아니라 응답 Body 데이터로 처리합니다.
@RestController
@RequestMapping("/api/boards")
public class BoardApiController {
@GetMapping("/{boardNo}")
public BoardDto detail(@PathVariable Long boardNo) {
return new BoardDto(boardNo, "Spring", "RestController 정리");
}
}
이 코드에서는 메서드마다 @ResponseBody를 붙이지 않아도 됩니다. @RestController가 클래스 전체에 응답 Body 반환 방식을 적용하기 때문입니다.
8. @Controller와 @RestController 차이
@Controller와 @RestController는 둘 다 요청을 처리하는 Controller를 만들 때 사용합니다. 하지만 반환값을 해석하는 방식이 다릅니다.
| 구분 | @Controller | @RestController |
| 주 사용 목적 | 화면 반환 | 데이터 반환 |
| 반환 문자열 | View 이름으로 해석될 수 있음 | 응답 Body 데이터로 반환 |
| 주 사용 화면 | JSP, Thymeleaf | REST API, JSON 응답 |
| 예시 | return "board/list"; |
return "ok"; |
처음에는 이렇게 기억하면 됩니다.
화면을 반환한다
→ @Controller
JSON 데이터를 반환한다
→ @RestController
9. 게시판 화면 Controller 예시
JSP나 Thymeleaf 화면을 반환하는 게시판 Controller는 보통 @Controller를 사용합니다.
@Controller
@RequestMapping("/board")
public class BoardController {
@GetMapping("/list")
public String list() {
return "board/list";
}
@GetMapping("/write")
public String writeForm() {
return "board/write";
}
}
이 구조에서 board/list와 board/write는 응답 데이터가 아니라 View 이름입니다. Spring은 해당 View를 찾아 화면을 보여줍니다.
GET /board/list
→ board/list View 반환
GET /board/write
→ board/write View 반환
10. 게시판 API Controller 예시
반대로 JSON 데이터를 반환하는 게시판 API는 @RestController를 사용할 수 있습니다.
@RestController
@RequestMapping("/api/boards")
public class BoardApiController {
@GetMapping
public List<BoardDto> list() {
return List.of(
new BoardDto(1L, "Spring", "목록 API"),
new BoardDto(2L, "MVC", "JSON 응답")
);
}
@GetMapping("/{boardNo}")
public BoardDto detail(@PathVariable Long boardNo) {
return new BoardDto(boardNo, "Spring", "상세 API");
}
}
이 Controller는 화면 이름을 반환하지 않습니다. 메서드 반환값이 JSON 응답으로 변환되어 클라이언트에게 전달됩니다.
GET /api/boards
→ 게시글 목록 JSON 응답
GET /api/boards/1
→ 1번 게시글 JSON 응답
11. @RequestBody와 함께 사용하기
API에서는 요청 JSON을 @RequestBody로 받고, 응답 데이터를 객체나 Map으로 반환하는 구조를 자주 사용합니다.
@RestController
@RequestMapping("/api/boards")
public class BoardApiController {
@PostMapping
public Map<String, Object> write(@RequestBody BoardDto boardDto) {
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "게시글 등록 완료");
return result;
}
}
요청과 응답 흐름은 다음과 같습니다.
클라이언트 JSON 요청
→ @RequestBody로 DTO 변환
→ Controller 처리
→ Map 반환
→ JSON 응답
이 구조는 React, Vue, JavaScript fetch 같은 프론트엔드와 JSON으로 통신할 때 자주 사용됩니다.
12. HttpMessageConverter란?
Spring이 Java 객체를 JSON으로 바꿔 응답하거나, JSON 요청을 Java 객체로 바꿀 때 내부적으로 메시지 변환 기능을 사용합니다. 이 역할을 하는 것이 HttpMessageConverter입니다.
요청 JSON
→ Java 객체로 변환
Java 객체
→ 응답 JSON으로 변환
처음부터 깊게 외울 필요는 없습니다. 다만 @RequestBody와 @ResponseBody가 JSON 변환과 관련이 있다는 점은 기억하면 좋습니다.
| @RequestBody | JSON 요청을 Java 객체로 변환합니다. |
| @ResponseBody | Java 객체를 JSON 응답으로 변환합니다. |
13. 자주 하는 실수
1) @Controller에서 @ResponseBody를 빼먹는 경우
@Controller에서 문자열을 데이터로 반환하고 싶은데 @ResponseBody를 빼먹으면 Spring이 View 이름으로 해석할 수 있습니다.
@Controller
public class TestController {
@GetMapping("/api/hello")
public String hello() {
return "hello";
}
}
위 코드는 hello라는 데이터를 반환하는 것이 아니라 hello라는 View를 찾으려고 할 수 있습니다. 데이터로 반환하려면 다음처럼 작성합니다.
@Controller
public class TestController {
@GetMapping("/api/hello")
@ResponseBody
public String hello() {
return "hello";
}
}
2) API Controller인데 @Controller를 계속 쓰는 경우
모든 메서드가 JSON 데이터를 반환하는 API Controller라면 @RestController를 사용하는 것이 더 깔끔합니다.
@RestController
@RequestMapping("/api/boards")
public class BoardApiController {
}
3) 화면 Controller에 @RestController를 붙이는 경우
화면을 반환해야 하는 Controller에 @RestController를 붙이면 문제가 생길 수 있습니다. 반환 문자열이 View 이름이 아니라 응답 데이터로 나가기 때문입니다.
@RestController
public class BoardController {
@GetMapping("/board/list")
public String list() {
return "board/list";
}
}
이 경우 board/list 화면을 찾는 것이 아니라 board/list라는 문자열이 그대로 응답될 수 있습니다. 화면을 반환하려면 @Controller를 사용해야 합니다.
4) DTO에 getter가 없어서 JSON 응답이 이상한 경우
객체를 JSON으로 변환하려면 DTO의 필드 값을 읽을 수 있어야 합니다. 일반적으로 getter가 필요합니다.
public class BoardDto {
private Long boardNo;
private String title;
public Long getBoardNo() {
return boardNo;
}
public String getTitle() {
return title;
}
}
Lombok을 사용한다면 @Getter나 @Data를 사용할 수도 있습니다. 처음에는 getter가 JSON 응답과 관련 있다는 점을 이해하면 됩니다.
5) 응답 구조를 매번 Map으로만 만드는 경우
간단한 응답에는 Map을 사용할 수 있지만, 응답 구조가 반복된다면 응답 DTO를 만드는 것이 좋습니다.
public class ApiResult {
private boolean success;
private String message;
public ApiResult(boolean success, String message) {
this.success = success;
this.message = message;
}
public boolean isSuccess() {
return success;
}
public String getMessage() {
return message;
}
}
@PostMapping("/api/boards")
public ApiResult write(@RequestBody BoardDto boardDto) {
return new ApiResult(true, "게시글 등록 완료");
}
14. 전체 흐름 정리
@RequestBody, @ResponseBody, @RestController의 흐름을 함께 정리하면 다음과 같습니다.
1. 클라이언트가 JSON 요청을 보낸다.
2. @RequestBody가 JSON을 Java 객체로 변환한다.
3. Controller가 Service를 호출해 처리한다.
4. Controller가 Java 객체를 반환한다.
5. @ResponseBody 또는 @RestController에 의해 응답 Body로 반환된다.
6. Java 객체가 JSON 응답으로 변환된다.
7. 클라이언트가 JSON 응답을 받는다.
즉, 요청과 응답이 모두 JSON 중심으로 움직이는 구조에서는 @RestController, @RequestBody, 객체 반환을 함께 사용하는 경우가 많습니다.
정리
@ResponseBody는 Controller 메서드의 반환값을 View 이름으로 해석하지 않고, HTTP 응답 Body에 직접 담아 반환하게 해주는 어노테이션입니다. 문자열, 객체, List, Map 등을 응답 데이터로 보낼 수 있습니다.
@RestController는 @Controller와 @ResponseBody를 합친 형태로 이해할 수 있습니다. API Controller처럼 모든 메서드가 데이터를 반환하는 경우에 자주 사용합니다.
| @Controller | 주로 View 화면을 반환합니다. |
| @ResponseBody | 반환값을 응답 Body 데이터로 직접 반환합니다. |
| @RestController | @Controller + @ResponseBody처럼 동작합니다. |
| @RequestBody | 요청 Body의 JSON을 Java 객체로 받습니다. |
처음에는 다음 기준으로 기억하면 좋습니다.
화면을 반환한다
→ @Controller
데이터를 직접 반환한다
→ @ResponseBody
API 전용 Controller를 만든다
→ @RestController
JSON 요청을 객체로 받는다
→ @RequestBody
연습해보기
아래 요구사항을 보고 Controller를 작성해보세요.
GET /api/members/1
→ 1번 회원 정보를 JSON으로 반환
POST /api/members
→ JSON으로 회원가입 데이터를 받고
→ 성공 여부를 JSON으로 반환
예시는 다음과 같습니다.
@RestController
@RequestMapping("/api/members")
public class MemberApiController {
@GetMapping("/{memberNo}")
public MemberDto detail(@PathVariable Long memberNo) {
return new MemberDto(memberNo, "springUser");
}
@PostMapping
public ApiResult join(@RequestBody MemberJoinDto memberJoinDto) {
return new ApiResult(true, "회원가입 완료");
}
}
다음 글에서는 API 응답을 더 세밀하게 다룰 수 있는 ResponseEntity에 대해 정리하겠습니다. 상태 코드, Header, Body를 함께 제어하는 방법을 알아보겠습니다.

'Tech Stack > Spring Boot' 카테고리의 다른 글
| [Spring] 24. @Valid와 BindingResult (0) | 2026.07.17 |
|---|---|
| [Spring] 23. ResponseEntity란? (0) | 2026.07.16 |
| [Spring] 21. @RequestBody란? (0) | 2026.07.14 |
| [Spring] 20. @ModelAttribute와 DTO 바인딩 (0) | 2026.07.13 |
| [Spring] 19. @PathVariable이란? (0) | 2026.07.12 |