🌐 @RequestBody, @ResponseBody vs HttpEntity(RequestEntity, ResponseEntity)
- 스프링에서 HTTP Body를 처리하는 5가지 방식 정리

 

REST API를 개발하다 보면 HTTP 요청과 응답의 본문(body)을 직접 다뤄야 할 일이 많습니다.

이때 사용할 수 있는 방법이 여러 가지 있는데, 대표적으로 다음과 같은 것들이 있습니다.

 

@RequestBody, @ResponseBody, HttpEntity, RequestEntity, ResponseEntity

 

이름이 비슷해서 헷갈리기 쉽지만, 의도와 활용 방식에 차이가 있습니다.

이번 글에서는 이 5가지의 차이를 명확하게 비교해보겠습니다.


1️⃣ @RequestBody란?

HTTP 요청의 body 내용을 자바 객체로 변환해주는 어노테이션입니다.
→ JSON, XML, plain text 등을 객체로 바꿔줍니다.

@PostMapping("/hello")
public String hello(@RequestBody UserDto user) {
    return "Hello " + user.getName();
}

특징

  • 요청 본문(JSON 등)을 객체로 바로 변환
  • 내부적으로 HttpMessageConverter가 동작
  • 필수 값 (기본적으로 body가 없으면 400 Bad Request 발생)

2️⃣ @ResponseBody란?

자바 객체를 HTTP 응답의 body로 직접 변환해주는 어노테이션입니다.
→ JSON, XML 등으로 자동 직렬화됩니다.

@GetMapping("/user")
@ResponseBody
public UserDto getUser() {
    return new UserDto("철준", 35);
}

특징

  • 반환값이 뷰 이름이 아닌 데이터 자체로 응답
  • @RestController를 쓰면 클래스 전체에 @ResponseBody 적용됨

3️⃣ HttpEntity<T>란?

요청 또는 응답에서 HTTP 본문(body) + 헤더 정보를 함께 가져오거나 응답할 수 있는 객체입니다.

@PostMapping("/entity")
public String handle(HttpEntity<UserDto> request) {
    HttpHeaders headers = request.getHeaders();
    UserDto body = request.getBody();
    ...
}

특징

  • 본문과 헤더를 동시에 받을 수 있음
  • 파라미터로만 사용 가능 (리턴 시에는 ResponseEntity를 씀)

4️⃣ RequestEntity<T>란?

HttpEntity를 상속한 클래스이며, 요청 메서드, URL, 헤더, 본문 등 더 많은 정보를 포함합니다.

 
@PostMapping("/request-entity")
public String handleRequest(RequestEntity<UserDto> request) {
    HttpMethod method = request.getMethod();
    URI url = request.getUrl();
    UserDto body = request.getBody();
    ...
}
  • HttpEntity + HTTP method, URL 정보 포함
  • 요청 정보를 보다 정밀하게 다루고 싶을 때 사용

5️⃣ ResponseEntity<T>란?

응답의 본문 + 상태 코드 + 헤더를 직접 제어할 수 있는 객체입니다.
→ 가장 많이 사용되는 응답 타입

@GetMapping("/response")
public ResponseEntity<UserDto> response() {
    UserDto user = new UserDto("철준", 35);
    return ResponseEntity
        .status(HttpStatus.CREATED)
        .header("Custom-Header", "hello")
        .body(user);
}

특징

  • HTTP 상태 코드, 헤더, 바디를 자유롭게 설정 가능
  • REST API 응답 커스터마이징에 최적

📊 전체 비교표

항목본문 처리헤더 접근상태 코드 설정사용 위치비고

 

항목 본문 처리 헤더 접근 상태 코드 설정 사용 위치 비고
@RequestBody 파라미터 요청 본문을 객체로 변환
@ResponseBody 반환값 객체를 JSON 등으로 응답
HttpEntity<T> 파라미터 헤더 + 바디 접근 가능
RequestEntity<T> 파라미터 HttpMethod, URL 정보 포함
ResponseEntity<T> 반환값 응답 제어용으로 자주 사용

💡 정리 포인트

  • @RequestBody는 클라이언트 → 서버 요청 본문(JSON 등)을 객체로 바꾸는 도구
  • @ResponseBody는 서버 → 클라이언트 응답을 객체 → JSON 등으로 바꾸는 도구
  • HttpEntity는 본문 + 헤더 모두 보고 싶을 때 사용
  • RequestEntity는 요청의 HTTP 메서드, URI까지 알고 싶을 때
  • ResponseEntity는 응답의 상태 코드, 헤더, 본문 모두 직접 조작할 수 있을 때 사용

✨ 마무리

Spring MVC에서 HTTP 요청과 응답을 다룰 때는 상황에 맞는 도구를 선택하는 것이 중요합니다.

  • 단순 요청이라면 @RequestBody, @ResponseBody로 간단하게 처리
  • 헤더나 상태 코드까지 조작이 필요하다면 HttpEntity, ResponseEntity를 활용하자.

 

 


 

 

 

🧰 HttpEntity, RequestEntity, ResponseEntity는 Body만 다룰까?
- HTTP 요청/응답의 전체 구조를 함께 다루는 '컨테이너' 입니다.

 

Spring MVC에서 HttpEntity, RequestEntity, ResponseEntity는 종종 "HTTP Body를 처리할 때 사용하는 객체"로 소개됩니다.

하지만 이 클래스들은 본문(body)뿐 아니라 헤더(header)까지 포함해서, 더 넓은 역할을 수행합니다.

이 클래스들은 "HTTP 요청/응답을 구성하는 모든 정보(본문, 헤더, 상태 코드 등)를 함께 담는 컨테이너"입니다.

📦 HTTP 요청/응답은 어떤 정보로 구성돼 있을까?

HTTP 메시지는 크게 다음 3가지로 구성됩니다:

  1. 상태 라인 / 요청 라인 (Request: GET /hello, Response: HTTP/1.1 200 OK)
  2. 헤더(Header) — Content-Type, Authorization 등
  3. 본문(Body) — JSON, XML, 텍스트 등 실질적인 데이터

☝️ 여기서 핵심 질문:

"스프링에서는 이 모든 걸 어떻게 한 번에 다룰 수 있을까?"

 

그때 등장하는 것이 바로:

  • HttpEntity<T>
  • RequestEntity<T>
  • ResponseEntity<T>

이들은 단순히 Body만 처리하는 것이 아니라,
HTTP 메시지를 이루는 구조 전체를 객체로 감싸서 통합적으로 다루는 도구입니다.


1️⃣ HttpEntity<T> - 요청/응답의 헤더 + 바디를 함께 담는 기본 컨테이너

public class HttpEntity<T> {
    private final HttpHeaders headers;
    private final T body;
}
  • headers: 요청 또는 응답의 HTTP 헤더
  • body: JSON 등 본문 데이터

✔ 특징

  • 요청과 응답 양쪽에서 사용 가능
  • 상태 라인(메서드, 상태코드)은 포함하지 않음

2️⃣ RequestEntity<T> - 요청 전체 정보를 모두 담고 싶은 경우

public class RequestEntity<T> extends HttpEntity<T> {
    private final HttpMethod method;
    private final URI url;
}
  • HttpEntity의 기능 + 요청 라인(HTTP 메서드, URL) 정보까지 포함
  • 예: POST /api/users, GET /api/data

✔ 특징

  • HTTP 요청 메시지의 구조 전체를 객체로 표현 가능
  • 요청 처리 전에 메서드나 URI 조건을 확인하고 분기할 수 있음

3️⃣ ResponseEntity<T> - 응답 전체 구조를 구성하고 반환하는 객체

public class ResponseEntity<T> extends HttpEntity<T> {
    private final HttpStatus status;
}
  • HttpEntity의 기능 + 응답 상태 코드(200 OK, 404 Not Found 등)까지 포함

✔ 특징

  • 바디, 헤더, 상태 코드 모두 커스터마이징 가능
  • 실제 운영 API에서 가장 자주 쓰이는 반환 타입

📊 전체 비교 정리

항목 바디 헤더 상태 코드 라인 정보(method, URL 등) 사용 목적
HttpEntity<T> 요청/응답 헤더+바디 처리
RequestEntity<T> 요청의 메서드, URL까지 필요할 때
ResponseEntity<T> 응답의 상태 코드까지 제어하고 싶을 때

 


💡 실전에서 언제 쓰일까?

요청 처리할 때

@PostMapping("/auth")
public String auth(HttpEntity<?> request) {
    HttpHeaders headers = request.getHeaders();
    // Authorization, User-Agent 등 확인 가능
    ...
}

→ 요청의 헤더와 바디를 함께 확인해야 할 때 유용


응답 보낼 때

@GetMapping("/download")
public ResponseEntity<Void> download() {
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Disposition", "attachment; filename=data.csv");
    return new ResponseEntity<>(headers, HttpStatus.OK);
}

본문 없이도 헤더와 상태코드를 명확하게 제어 가능


✨ 마무리 요약

  • HttpEntity, RequestEntity, ResponseEntity는 단순한 Body 전달용이 아님
    • → HTTP 메시지의 모든 구조(본문, 헤더, 상태 코드, 요청 메서드 등)를 객체 형태로 다루는 도구
  • 상황에 따라 필요한 정보를 얼마나 정밀하게 제어할 것인지에 따라 적절한 클래스를 선택하면 됩니다.

 

 

 

 

🏷️ DTYPE 컬럼

상속 매핑에서 사용하는 구분 컬럼 (Discriminator Column)

JPA에서는 엔티티 간에 상속 관계를 매핑할 수 있습니다. 그리고 이때 사용되는 특별한 컬럼이 바로 DTYPE입니다.
처음에는 생소할 수 있지만, 그 역할을 이해하면 굉장히 유용한 기능입니다.


1️⃣ DTYPE이란?

DTYPE은 JPA에서 상속 매핑 시, 어떤 하위 엔티티 타입인지 구분하기 위해 자동 생성되는 컬럼입니다.

쉽게 말해, 하나의 테이블에 여러 타입의 자식 엔티티 데이터를 함께 저장할 때, "이 레코드는 어떤 자식 클래스에 해당하는지 알려주는 표시"예요.

예를 들어:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
}
@Entity
public class Book extends Item {
    private String author;
}
@Entity
public class Album extends Item {
    private String artist;
}

이렇게 상속 구조가 있을 때, JPA는 Item 테이블을 만들고, Book과 Album 데이터를 같은 테이블에 저장합니다.

이때 "이 데이터가 Book인지 Album인지 어떻게 구분할까?"
→ 그걸 위해 생기는 컬럼이 바로 DTYPE입니다.


2️⃣ 생성되는 테이블 예시

id name author artist DTYPE
1 자바의 정석 남궁성 null Book
2 힙합 앨범 null 박재범 Album
  • JPA가 자동으로 DTYPE 컬럼을 만들고, 해당 엔티티의 클래스 이름을 저장합니다.
  • 이 정보는 JPA가 조회할 때, 어떤 클래스로 데이터를 변환할지 결정하는 데 사용됩니다.

3️⃣ @DiscriminatorColumn으로 이름 변경 가능

기본적으로 DTYPE이라는 컬럼명이 생성되지만, 직접 이름을 지정할 수도 있어요.

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "item_type")
public abstract class Item {
    ...
}

이렇게 하면 item_type이라는 이름으로 컬럼이 생성됩니다.


4️⃣ DTYPE이 사용되는 상속 전략

상속 전략 DTYPE 사용 여부 설명
SINGLE_TABLE (단일 테이블 전략) ✅ 사용함 모든 자식 엔티티를 한 테이블에 저장하므로 반드시 필요
JOINED (조인 전략) ✅ 사용함 테이블은 나뉘지만, 부모 테이블에 구분값이 필요함
TABLE_PER_CLASS (각자 테이블 전략) ❌ 사용 안 함 테이블이 완전히 분리되므로 필요 없음

 

5️⃣ 조회할 때 어떻게 쓰이나요?

Item item = entityManager.find(Item.class, 1L);
// DTYPE 컬럼이 Book이면 Book 객체로 자동 매핑됨
  • DTYPE 값이 Book이면 Book 객체로, Album이면 Album 객체로 자동 변환됩니다.
  • 즉, DTYPE은 JPA의 다형성 구현을 위한 핵심 키워드입니다.

✅ 요약

항목 내용
역할 상속 관계에서 실제 객체 타입을 구분하기 위한 컬럼
기본 컬럼명 DTYPE
커스터마이징 @DiscriminatorColumn(name = "구분값")
사용 전략 SINGLE_TABLE, JOINED 전략에서 사용됨
값의 의미 실제 저장된 자식 클래스 이름 (기본값)

 

✨ 마무리

JPA에서 DTYPE은 상속 매핑의 다형성 지원을 위한 중요한 컬럼입니다.

무심코 생성된 것처럼 보일 수 있지만, 실제로는 자식 엔티티를 구분하고 올바른 타입으로 조회하는 데 핵심적인 역할을 합니다.

실무에서 상속 매핑을 사용할 일이 있다면 꼭 알아두면 좋습니다.

 

 

💾 CLOB vs BLOB - 대용량 데이터를 저장하는 두 가지 방식

 

RDBMS(관계형 데이터베이스)에서는 종종 텍스트나 파일 같은 대용량 데이터를 다뤄야 할 때가 있습니다.
이럴 때 사용하는 대표적인 데이터 타입이 바로 CLOB(Character Large Object)와 BLOB(Binary Large Object)입니다.
이번 글에서는 이 둘의 차이와 활용 방법에 대해 정리해 보겠습니다.


📌 1. CLOB이란?

CLOB(Character Large Object)는 대용량 텍스트 데이터를 저장하기 위한 데이터 타입입니다.

예시

  • 장문의 기사 본문
  • JSON/XML 형식의 긴 설정값
  • 문서 내용 (예: 계약서, 보고서 등)

특징

  • 문자열(문자 기반) 데이터 전용
  • 일반적으로 최대 수십 MB~수 GB까지 저장 가능 (DBMS마다 다름)
  • JDBC에서는 java.sql.Clob으로 처리됨

📌 2. BLOB이란?

BLOB(Binary Large Object)는 이미지, 동영상, PDF, 실행파일 등 바이너리 데이터를 저장하는 타입입니다.

예시

  • 이미지 파일 (JPG, PNG 등)
  • 동영상 파일
  • 바이너리 파일(exe, zip 등)

특징

  • 비정형(바이너리) 데이터 전용
  • 텍스트가 아닌 데이터는 반드시 BLOB을 사용
  • JDBC에서는 java.sql.Blob으로 처리됨

📊 CLOB vs BLOB 비교 정리

구분 CLOB BLOB
의미 Character Large Object Binary Large Object
용도 긴 텍스트 저장 이미지, 파일 등 바이너리 저장
데이터 형태 문자 기반 (UTF-8 등 인코딩) 바이너리 기반 (byte)
JDBC 타입 java.sql.Clob java.sql.Blob
처리 방식 Reader / Writer InputStream / OutputStream
주 사용 예시 뉴스 본문, 소설, XML, JSON 사진, 동영상, PDF, ZIP 등

🛠️ 사용 예시 (JDBC 기준)

✅ CLOB 저장

PreparedStatement ps = conn.prepareStatement("INSERT INTO article (title, content) VALUES (?, ?)");
ps.setString(1, "클롭 예제");
ps.setClob(2, new StringReader("긴 텍스트 내용..."));
ps.executeUpdate();

✅ BLOB 저장

PreparedStatement ps = conn.prepareStatement("INSERT INTO files (filename, data) VALUES (?, ?)");
ps.setString(1, "image.jpg");
ps.setBlob(2, new FileInputStream(new File("image.jpg")));
ps.executeUpdate();

 


💡 스프링(Spring)에서는 어떻게 다룰까?

Spring JDBC 또는 JPA에서도 CLOB/BLOB 처리는 가능합니다.

JPA 예시

@Lob
private String content; // CLOB 처리됨

@Lob
private byte[] fileData; // BLOB 처리됨
  • @Lob 어노테이션은 필드를 CLOB 또는 BLOB으로 자동 매핑합니다.
  • String이면 CLOB, byte[]이면 BLOB으로 판단함

⚠️ 주의할 점

  1. 성능 이슈: CLOB/BLOB은 많은 I/O를 유발하므로 트래픽이 많은 서비스에서 남용은 금물입니다.
  2. 파일 vs DB 저장 고민:
    • 파일을 DB에 직접 저장하지 않고, 파일 경로만 저장하고 실제 파일은 서버나 S3 등에 저장하는 방식도 많이 씁니다.
  3. DBMS별 제한: Oracle, MySQL, PostgreSQL 등 DB마다 최대 크기와 처리 방식이 다르니 문서 확인 필수입니다.
  4. 트랜잭션 크기: 너무 큰 BLOB 데이터를 저장하거나 수정할 경우 트랜잭션 크기 제한에 걸릴 수 있음

📚 정리

질문 답변
텍스트 저장하려면? CLOB
파일/이미지 저장하려면? BLOB
문자 기반인가요? CLOB는 문자, BLOB은 바이너리
JPA에서는 어떻게? @Lob + String 또는 byte[]
직접 DB에 저장해도 되나요? 가능하지만, 파일 시스템 저장이 더 효율적일 수 있음

 

📝 마무리

CLOB과 BLOB은 데이터베이스에서 대용량 데이터를 다룰 때 반드시 알아야 할 개념입니다.

용도에 맞게 잘 활용하면 유용하지만, 과도하게 사용하면 성능 문제를 초래할 수 있습니다.

실제 프로젝트에서는 "직접 DB에 저장할지, 파일로 분리할지" 고민한 후 선택하는 것이 중요합니다.

 

🧩 ObjectMapper란? - 자바에서 JSON을 다루는 가장 강력한 도구

웹 개발에서 JSON은 가장 많이 사용되는 데이터 포맷입니다.

특히 Spring Boot를 사용하면 REST API에서 JSON은 기본값이죠.

이때 자바 객체 ↔ JSON 간 변환을 손쉽게 도와주는 것이 바로 Jackson의 ObjectMapper입니다.


1️⃣ ObjectMapper란?

ObjectMapper는 Jackson 라이브러리에서 제공하는 클래스이며, 자바 객체를 JSON으로 직렬화하거나(JSON 생성), JSON 문자열을 자바 객체로 역직렬화할 때(JSON 파싱) 사용됩니다.

com.fasterxml.jackson.databind.ObjectMapper

2️⃣ 주요 기능

기능 설명
직렬화 (Serialization) 자바 객체 → JSON 문자열
역직렬화 (Deserialization) JSON 문자열 → 자바 객체
읽기/쓰기 지원 파일, InputStream, 문자열, 바이트 등 다양한 입력/출력 지원
유연한 설정 가능 필드 명명 전략, Null 처리, 날짜 포맷 등 커스터마이징 가능

3️⃣ 사용 예제

✅ JSON → 자바 객체 (Deserialization)

String json = "{\"name\":\"철준\",\"age\":35}";

ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(json, Person.class);

System.out.println(person.getName()); // 철준
public class Person {
    private String name;
    private int age;
    
    // 기본 생성자 & getter/setter 필요!
}

✅ 자바 객체 → JSON (Serialization)

Person person = new Person();
person.setName("철준");
person.setAge(35);

ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(person);

System.out.println(json); 
// 결과: {"name":"철준","age":35}

4️⃣ 자주 쓰는 메서드 정리

메서드 설명
writeValueAsString(Object obj) 객체 → JSON 문자열
writeValue(File/file/OutputStream, Object obj) 객체를 파일 또는 스트림에 JSON으로 저장
readValue(String json, Class<T> clazz) JSON 문자열 → 객체
readTree(String json) JSON 문자열을 트리 구조(JsonNode)로 파싱
convertValue(Object fromValue, Class<T> toValueType) 객체 간 타입 변환 (ex. DTO ↔ Map)

5️⃣ JSON 트리 다루기 (JsonNode)

단순한 매핑이 아니라, 노드 단위로 JSON을 탐색하고 싶을 때는 readTree()를 사용합니다.

String json = "{\"user\":{\"name\":\"철준\",\"age\":35}}";
JsonNode root = objectMapper.readTree(json);
String name = root.path("user").path("name").asText();

System.out.println(name); // 철준

6️⃣ 주의할 점

  • 객체를 JSON으로 변환하려면 기본 생성자와 getter/setter가 반드시 필요합니다.
  • 필드명이 JSON 키와 다를 경우 @JsonProperty("jsonKey")를 이용해야 합니다.
  • 날짜, null 처리 등은 기본 설정이 예상과 다를 수 있으므로 커스터마이징이 필요할 수 있습니다.
@JsonProperty("user_name")
private String userName;

7️⃣ Spring Boot에서 자동으로 쓰이는 이유

Spring Boot는 내부적으로 Jackson의 ObjectMapper를 기본 메시지 컨버터로 사용합니다.
즉, @RestController에서 return 객체를 하면 자동으로 JSON으로 변환해주는 이유도 이 때문입니다.

@GetMapping("/user")
public Person user() {
    return new Person("철준", 35); // 자동으로 JSON 변환됨
}

✅ 마무리

ObjectMapper는 JSON을 다룰 때 없어서는 안 될 핵심 도구입니다.
직렬화/역직렬화는 물론이고, 유연한 커스터마이징, 트리 탐색, 타입 변환 등 다양한 기능을 제공합니다.
특히 Spring Boot에서는 기본값으로 쓰이기 때문에, 잘 익혀두면 JSON 처리에서 자유로워질 수 있습니다.


 

참조

📌 StreamUtils.copyToString() — InputStream을 String으로 쉽게 변환하는 방법

Spring 기반 웹 애플리케이션을 개발하다 보면, 클라이언트로부터 전달받은 HTTP 요청의 InputStream을 문자열로 읽어야 할 때가 있습니다. 이때 유용하게 사용할 수 있는 도구가 바로 StreamUtils.copyToString()입니다.


🔍 StreamUtils.copyToString()이란?

StreamUtils.copyToString()은 Spring Framework에서 제공하는 유틸리티 메서드로, InputStream으로부터 전달받은 바이트 데이터를 문자열(String)로 간편하게 변환해주는 기능을 합니다.

public static String copyToString(InputStream in, Charset charset) throws IOException
  • in: 읽을 대상 InputStream (예: 요청 본문)
  • charset: 변환 시 사용할 문자 인코딩 (예: StandardCharsets.UTF_8)

✅ 어떤 상황에서 쓰일까?

  • 클라이언트가 POST나 PUT 방식으로 JSON, XML, 혹은 일반 텍스트 데이터를 보낼 때
  • 필터나 인터셉터에서 요청 본문을 직접 읽어야 하는 경우
  • 서블릿에서 InputStream을 문자열로 바꾸고 싶을 때

💡 사용 예제

@PostMapping("/example")
public void example(HttpServletRequest request) throws IOException {
    String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
    System.out.println("요청 본문: " + body);
}

위 코드는 다음과 같은 요청에서:

POST /example
Content-Type: application/json

{"name":"철준", "age":35}

다음과 같은 출력 결과를 만듭니다:

요청 본문: {"name":"철준", "age":35}

⚠️ 주의할 점

  1. InputStream은 한 번만 읽을 수 있음!
    • copyToString()을 사용한 후에는 같은 InputStream을 다시 읽을 수 없습니다.
    • 여러 번 사용하려면 RequestWrapper를 만들어 캐싱하거나 별도 처리를 해야 합니다.
  2. 인코딩 설정은 반드시 명시해야 함
    • 클라이언트가 보낸 데이터가 UTF-8이 아닐 경우 StandardCharsets.UTF_8로 읽으면 깨질 수 있습니다.
    • Content-Type의 charset 정보를 확인하거나, UTF-8로 통일되게 처리하는 것이 좋습니다.

📦 어디에 정의되어 있나?

StreamUtils는 Spring Core 모듈에 포함되어 있으며, 다음 패키지에 위치해 있습니다:

org.springframework.util.StreamUtils

의존성은 따로 추가할 필요 없이, 스프링 부트 프로젝트에서는 기본적으로 포함되어 있습니다.


✨ 요약

항목 설명
목적 InputStream → String 변환
위치 org.springframework.util.StreamUtils
주요 사용 예 요청 본문 읽기, JSON 처리, 필터/인터셉터 처리 등
주요 주의 사항 InputStream은 한 번만 읽을 수 있음, 인코딩 주의

✅ 마무리

StreamUtils.copyToString()은 간단하지만 스프링 개발에서 꽤 자주 쓰이는 핵심 유틸 메서드입니다.
특히 요청 본문을 직접 다루거나, 인터셉터에서 로그를 찍을 때 매우 유용하게 사용할 수 있으니 기억해두면 실무에서 큰 도움이 될 것 입니다.

 

 

 

참조:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/StreamUtils.html 

 

 

 

response.getWriter().write() vs return "ok"

Spring MVC를 처음 접하거나, 직접 서블릿 API를 다뤄본 개발자라면 한 번쯤은 다음 두 코드의 차이에 대해 궁금했던 적이 있을 것입니다:

// 방식 1: HttpServletResponse 사용
response.getWriter().write("ok");

// 방식 2: @ResponseBody와 return
@ResponseBody
public String hello() {
    return "ok";
}

이 두 방식은 모두 클라이언트에게 HTTP 응답을 반환하지만, 사용 방식과 처리 흐름에 큰 차이가 있습니다.

이번 글에서는 이 차이를 정리하고, 어떤 상황에서 어떤 방식을 사용하는 것이 좋은지 알아보겠습니다.


1. response.getWriter().write("ok"): 서블릿 API 직접 사용

이 방식은 HttpServletResponse 객체를 통해 직접 응답 본문에 값을 써넣는 저수준 방식입니다.

@Controller
public class ResponseController {

    @RequestMapping("/manual-response")
    public void manualResponse(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }
}

특징

  • HttpServletResponse의 getWriter()를 통해 PrintWriter를 얻고, 직접 문자열을 씁니다.
  • Spring의 뷰 리졸버(ViewResolver)나 HttpMessageConverter를 거치지 않습니다.
  • 스프링의 생태계를 우회하고 직접 응답을 제어해야 할 때 사용합니다.
  • 예: 파일 다운로드, 이미지 응답, 바이너리 응답 등.

2. return "ok" + @ResponseBody: 스프링 방식의 간결한 처리

반면, 스프링 MVC에서는 메소드 반환값을 HTTP 응답으로 보내고 싶을 때 @RestController 또는 @ResponseBody를 활용할 수 있습니다.

@RestController
public class RestApiController {

    @GetMapping("/spring-response")
    public String springResponse() {
        return "ok";
    }
}

 

※ 또는 @Controller에 @ResponseBody를 붙이면 동일한 효과입니다.

@Controller
public class RestApiController {

    @ResponseBody
    @GetMapping("/spring-response")
    public String springResponse() {
        return "ok";
    }
}

특징

  • @ResponseBody가 붙으면 반환값은 뷰 이름으로 인식되지 않고, HTTP 응답 본문으로 전송됩니다.
  • 내부적으로 HttpMessageConverter가 동작하여 문자열, JSON 등으로 자동 변환됩니다.
  • RESTful API나 간단한 문자열 응답을 만들 때 매우 유용합니다.

3. 어떤 방식을 써야 할까?

기준 response.getWriter().write() @ResponseBody + return
처리 수준 저수준 (서블릿 직접 제어) 고수준 (스프링에서 처리)
코드 간결성 비교적 복잡 매우 간결
뷰/메시지 변환기 사용 여부 사용하지 않음 HttpMessageConverter 사용
사용 사례 파일 다운로드, 이미지 응답 등 문자열, JSON 응답 등 일반적인 API 응답
추천 여부 특별한 제어가 필요한 경우만 추천 대부분의 경우 적극 추천

4. 정리

  • 직접 제어가 필요한 특별한 상황(파일 처리, 비동기 스트림 등)이 아니라면, 스프링이 제공하는 @ResponseBody 방식이 훨씬 간편하고 유지보수에 유리합니다.
  • REST API 개발 시에는 @RestController를 사용하면 @ResponseBody가 자동 적용되므로 더욱 편리합니다.
  • 스프링은 단순히 편리함을 넘어, 개발자의 생산성과 유지보수성을 높여주는 강력한 추상화 도구임을 기억하면 좋습니다.

 

참고 링크

+ Recent posts