자바 백엔드 입문 28편 — @RestController와 JSON 응답

2026-05-16자바 백엔드 입문

자바 백엔드 입문 28편. @RestController가 JSON을 어떻게 자동 변환하고 Jackson이 어떤 역할을 하는지, ResponseEntity로 응답 코드·헤더 제어하는 법을 통역사 비유로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 28편 — @RestController와 JSON 응답

이 글은 자바 백엔드 입문 시리즈 59편 중 28편이에요. 27편에서 컨트롤러 매핑 패턴을 잡았다면, 이번 28편은 @RestController 가 어떻게 자바 객체를 JSON으로 자동 변환해주는지의 내부 동작을 풀어 가요. REST API의 90% 흐름이 여기 들어 있어요.

JSON 응답이 헷갈리는 이유

처음 @RestController 의 메서드에서 Order 객체를 반환하는 코드를 보면 — "그냥 자바 객체인데 어떻게 브라우저에선 JSON으로 보일까?" 가 안 잡혀요. 마법처럼 동작하는 그 사이에 Jackson 이라는 라이브러리가 있어요.

이 글에서는 통역사 비유로 풀어요. 컨트롤러 = 한국말로 보고하는 직원, 브라우저 = 영어만 이해하는 외국 클라이언트, Jackson = 가운데서 자동 통역하는 통역사. 끝까지 따라오시면 JSON 응답의 전체 그림이 한 번에 들어와요.

@RestController = @Controller + @ResponseBody

먼저 @RestController 의 정체부터. 8편에서 짧게 다뤘는데, 정확히는 두 어노테이션의 합이에요.

@Controller
@ResponseBody     // 메서드 반환값을 응답 본문으로 직접
public class OrderController { ... }

// 위 두 줄을 한 줄로
@RestController
public class OrderController { ... }

@Controller 단독 = 뷰 템플릿 렌더(HTML), @RestController = JSON·문자열 등을 응답 본문으로 직접 반환. 현대 백엔드의 95%가 @RestController.

자바 객체 → JSON 자동 변환 흐름

컨트롤러가 Order 객체를 반환하면 — 다음 흐름으로 JSON이 만들어져요.

[1] 컨트롤러 메서드 반환 → return new Order(...)
[2] DispatcherServlet이 받음
[3] HttpMessageConverter 후보들 중 적절한 것 찾기
[4] Jackson MappingJackson2HttpMessageConverter 선택
[5] Order 객체를 JSON 문자열로 직렬화
[6] HTTP 응답 본문에 박음 (Content-Type: application/json)
[7] 브라우저에 응답

핵심 부품 = HttpMessageConverter. Spring이 가지고 있는 "변환기 풀" 에서 "이 객체와 클라이언트의 Accept 헤더를 보고 어느 변환기를 쓸지" 자동 선택해줘요. 기본 변환기 = Jackson(MappingJackson2HttpMessageConverter).

Jackson — JSON 직렬화의 표준

Jackson은 자바 진영의 거의 표준 JSON 라이브러리예요. Spring Boot가 spring-boot-starter-web 의존성에 자동 포함시켜요. 우리가 별도 설정 안 해도 JSON 변환이 동작.

기본 동작 — "객체의 모든 public getter 메서드를 호출해 JSON 필드로 변환". 예를 들어:

public class Order {
    private Long id;
    private int amount;
    private LocalDateTime createdAt;

    public Long getId() { return id; }
    public int getAmount() { return amount; }
    public LocalDateTime getCreatedAt() { return createdAt; }
}

// 반환 시 JSON:
// {"id": 123, "amount": 10000, "createdAt": "2026-05-16T12:00:00"}

Order 의 3개 getter가 자동으로 JSON 필드 3개로. Lombok @Getter 박으면 getter 자동 생성이라 — @Data·@Getter 한 줄만 박아도 JSON 변환 동작.

Jackson 직렬화 제어 — @JsonProperty·@JsonIgnore

기본 동작으로 부족하면 어노테이션으로 미세 제어 가능.

@JsonProperty — 필드 이름 변경

public class Order {
    @JsonProperty("order_id")    // JSON에서는 order_id로
    private Long id;
}
// {"order_id": 123}

자바는 camelCase, JSON은 snake_case 컨벤션일 때 자주 쓰여요. 또는 "외부 API와 키 이름 맞춰야 할 때".

@JsonIgnore — 직렬화 제외

public class User {
    private Long id;
    private String name;

    @JsonIgnore                   // 응답에 절대 안 들어감
    private String password;
}

비밀번호·내부 토큰 같이 "클라이언트에 절대 노출하면 안 되는 필드" 차단. 보안의 첫 방어선.

@JsonFormat — 날짜·시간 포맷

public class Order {
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;
}
// "createdAt": "2026-05-16 12:30:45"

기본 LocalDateTime 직렬화 형태(2026-05-16T12:30:45)가 마음에 안 들 때.

@JsonInclude — null 필드 제외

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Order {
    private Long id;
    private String description;    // null이면 응답에 안 들어감
}

전역 설정도 가능 — application.ymlspring.jackson.default-property-inclusion: non_null.

ResponseEntity — 응답 코드·헤더까지 제어

기본 패턴 — "객체 반환하면 200 OK + JSON". 하지만 응답 상태 코드나 헤더를 제어하려면 ResponseEntity<T> 를 반환해요.

@PostMapping
public ResponseEntity<Order> create(@RequestBody OrderRequest req) {
    Order saved = orderService.create(req);
    return ResponseEntity
            .status(HttpStatus.CREATED)              // 201
            .header("Location", "/orders/" + saved.getId())
            .body(saved);
}

@GetMapping("/{id}")
public ResponseEntity<Order> get(@PathVariable Long id) {
    return orderService.findById(id)
            .map(ResponseEntity::ok)                  // 200 + body
            .orElse(ResponseEntity.notFound().build()); // 404
}

ResponseEntity 가 응답의 모든 것을 제어. HTTP 상태 코드·헤더·본문 셋 다 한 객체로 표현. REST API에서 자주 등장하는 패턴이에요.

자주 쓰이는 빌더:

메서드 효과
ResponseEntity.ok(body) 200 OK + 본문
ResponseEntity.status(HttpStatus.CREATED).body(...) 201 Created + 본문
ResponseEntity.notFound().build() 404 Not Found
ResponseEntity.badRequest().body(error) 400 + 오류 본문
ResponseEntity.noContent().build() 204 No Content (응답 본문 없음)

@ResponseStatus — 간단한 상태 코드 변경

ResponseEntity 까지 안 가도 상태 코드만 바꾸고 싶을 때 @ResponseStatus 한 줄.

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Order create(@RequestBody OrderRequest req) {
    return orderService.create(req);
}
// 응답 = 201 Created + JSON

예외 클래스에 박으면 더 강력해요. 23편 예외 처리에서 자세히.

Jackson 글로벌 설정 — application.yml

매번 어노테이션 박기 번거로우면 application.yml 또는 application.properties 에 글로벌 설정.

spring:
  jackson:
    property-naming-strategy: SNAKE_CASE              # 모든 필드 snake_case
    default-property-inclusion: non_null              # null 필드 자동 제외
    date-format: yyyy-MM-dd HH:mm:ss                  # 날짜 포맷
    time-zone: Asia/Seoul                              # 시간대

이걸 한 번 박아두면 — 모든 컨트롤러의 JSON 응답이 일관된 포맷으로 자동 정렬돼요. 실무 표준 패턴.

⚠️ 엔티티 직접 반환 함정

JPA @Entity 객체를 컨트롤러에서 직접 반환하지 마세요. 지연 로딩(LazyLoading) 함정이 발생해요. 트랜잭션이 닫힌 후 Jackson이 getter를 호출하면 LazyInitializationException 폭발. DTO를 따로 만들어 변환 후 반환이 표준 패턴. 30·32편 JPA에서 자세히.

한국 회사 백엔드 표준 — DTO 패턴

위 함정 때문에 한국 회사 백엔드는 거의 다음 패턴이에요.

// 1. JPA Entity (DB 매핑 전용)
@Entity
public class Order { ... }

// 2. Response DTO (API 응답 전용)
@Getter
@AllArgsConstructor
public class OrderResponse {
    private Long id;
    private int amount;
    private String status;

    public static OrderResponse from(Order entity) {
        return new OrderResponse(entity.getId(), entity.getAmount(), entity.getStatus());
    }
}

// 3. 컨트롤러 — DTO 반환
@GetMapping("/{id}")
public OrderResponse get(@PathVariable Long id) {
    return OrderResponse.from(orderService.findById(id));
}

Entity와 DTO 분리. DB 모델과 API 응답 모델이 독립적이라 — 한쪽이 바뀌어도 다른 쪽이 안 깨져요. 코드 양이 많아 보이지만 한국 백엔드의 표준 패턴.

한 줄 정리 — @RestController = @Controller + @ResponseBody. Jackson이 자바 객체를 JSON으로 자동 변환. ResponseEntity 로 상태 코드·헤더 제어. Entity 직접 반환 X, DTO 패턴 표준.

시험 직전 한 번 더 — JSON 응답 입문자가 매번 헷갈리는 것

  • @RestController = @Controller + @ResponseBody 합친 어노테이션
  • @Controller 단독 = HTML 뷰 렌더, @RestController = JSON 직접 반환
  • 현대 백엔드 95% = @RestController
  • 자바 객체 → JSON 자동 변환 = HttpMessageConverter 가 처리
  • 기본 변환기 = Jackson (Spring Boot 자동 포함)
  • Jackson 기본 = public getter 메서드를 JSON 필드로
  • Lombok @Getter·@Data 박으면 자동 동작
  • @JsonProperty("name") = JSON 필드 이름 변경
  • @JsonIgnore = 직렬화 제외 (비밀번호 등 보안)
  • @JsonFormat = 날짜·시간 포맷 지정
  • @JsonInclude(NON_NULL) = null 필드 제외
  • ResponseEntity<T> = 응답 상태 코드·헤더·본문 모두 제어
  • ResponseEntity.ok(body) = 200, .status(CREATED).body(...) = 201
  • ResponseEntity.notFound().build() = 404, .noContent().build() = 204
  • @ResponseStatus = 상태 코드만 간단히 변경
  • application.ymlspring.jackson.* = 글로벌 설정
  • JPA Entity 직접 반환 X — LazyLoading 함정
  • DTO 패턴 = Entity와 분리된 응답 전용 객체 (한국 백엔드 표준)
  • 정적 메서드 OrderResponse.from(entity) = DTO 변환 표준 패턴
  • MapStruct 같은 라이브러리로 DTO 변환 자동화 가능
  • JSON 응답 = Content-Type: application/json; charset=utf-8 자동

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!