자바 백엔드 입문 29편. HTTP 요청 데이터를 자바 메서드 매개변수로 받는 세 가지 표준 어노테이션 @RequestParam·@PathVariable·@RequestBody를 택배 송장 비유로 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 29편이에요. 28편에서 "자바 객체를 어떻게 JSON 응답으로 보내는가" 를 풀었다면, 이번 29편은 그 반대 방향 — "클라이언트가 보낸 데이터를 어떻게 자바 매개변수로 받는가" 를 다룹니다. 세 가지 표준 어노테이션이 등장해요.
요청 데이터 추출이 헷갈리는 이유
HTTP 요청 안에는 데이터가 들어올 자리가 여러 군데예요. URL 경로 · 쿼리 스트링 · 헤더 · 본문. 각각 어떤 어노테이션으로 받아야 하는지가 처음에 안 잡혀요.
이 글에서는 택배 송장 비유로 풀어요. URL 경로 = 송장의 "수신 주소", 쿼리 스트링 = 송장 "옵션 메모", 본문 = "택배 박스 안 내용물". 끝까지 따라오시면 세 어노테이션 + 보너스 둘 = 총 5종이 한 그림에 들어와요.
한눈에 보는 5종 어노테이션
| 어노테이션 | 추출 위치 | 사용 예 |
|---|---|---|
@PathVariable |
URL 경로 안 {...} |
/orders/{id} |
@RequestParam |
쿼리 스트링 또는 form 파라미터 | /orders?status=active |
@RequestBody |
HTTP 요청 본문 (JSON 등) | POST/PUT 본문 |
@RequestHeader |
HTTP 헤더 | Authorization: Bearer ... |
@CookieValue |
쿠키 | Cookie: SESSION=... |
앞 세 개가 90%. 뒤 두 개는 가끔 만나는 보조.
@PathVariable — URL 경로 안의 변수
@PathVariable 은 URL 경로 안 {...} 자리에서 값을 추출.
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
return orderService.findById(id);
}
// GET /orders/123 → id = 123L
매개변수 이름이 URL 변수 이름과 같으면 자동 매핑. 다르면 명시:
@GetMapping("/orders/{id}")
public Order get(@PathVariable("id") Long orderId) { ... }
여러 개 동시에:
@GetMapping("/orders/{orderId}/items/{itemId}")
public Item getItem(@PathVariable Long orderId, @PathVariable Long itemId) { ... }
타입 변환은 Spring이 자동. Long 으로 받으면 문자열 "123" 을 123L 로 자동 변환. 변환 실패 시 400 Bad Request 자동.
언제 쓰나 — REST API의 "리소스 식별자". /orders/{id}·/users/{userId}/posts/{postId} 같이 "무엇을 가리키는지" 가 URL 자체에 박힐 때.
@RequestParam — 쿼리 스트링 또는 form
@RequestParam 은 쿼리 스트링(?key=value) 또는 application/x-www-form-urlencoded form 본문에서 추출.
@GetMapping("/orders")
public List<Order> list(@RequestParam(required = false) String status,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
return orderService.findAll(status, page, size);
}
// GET /orders?status=active&page=2&size=10 → status="active", page=2, size=10
// GET /orders → status=null, page=1, size=20
3가지 옵션:
| 옵션 | 의미 |
|---|---|
value (또는 name) |
쿼리 키 이름 (생략 시 매개변수 이름) |
required |
필수 여부. 기본 true (없으면 400) |
defaultValue |
없을 때 기본값 |
required = false + defaultValue 둘 중 하나가 거의 표준 — "클라이언트가 안 보내도 동작" 보장.
리스트로도 받기:
@GetMapping("/products")
public List<Product> byCategories(@RequestParam List<String> categories) { ... }
// GET /products?categories=electronics&categories=clothing
// → categories = ["electronics", "clothing"]
언제 쓰나 — "검색·필터·정렬·페이징" 같이 "선택적이거나 다양하게 조합되는" 파라미터.
@RequestBody — JSON 본문
@RequestBody 는 HTTP 요청 본문 전체를 자바 객체로 변환. JSON·XML 등 Jackson(또는 다른 컨버터)이 알아서 매핑.
@PostMapping("/orders")
public Order create(@RequestBody OrderRequest req) {
return orderService.create(req);
}
// 클라이언트 요청:
// POST /orders
// Content-Type: application/json
// {"productId": 1, "quantity": 2}
// req 객체에 자동 매핑됨
DTO 클래스 한 개 정의 필요.
@Getter @Setter // Jackson은 setter 또는 필드 접근 둘 다 OK
public class OrderRequest {
private Long productId;
private int quantity;
}
여기서 시험 함정 자주 나와요. "@RequestBody 받는 DTO에 기본 생성자 필요한가?" — 답은 필요해요. Jackson이 객체를 만들 때 기본 생성자 호출. @AllArgsConstructor 만 박고 기본 생성자 없으면 — 데시리얼라이즈 실패. Lombok @NoArgsConstructor 도 같이 박거나, 별도 @JsonCreator 생성자 명시.
언제 쓰나 — POST·PUT·PATCH 요청에서 JSON 본문을 받을 때. REST API의 99% 패턴.
@RequestParam vs @RequestBody — 자주 헷갈리는 비교
이 둘이 가장 혼동되는 부분.
@RequestParam |
@RequestBody |
|
|---|---|---|
| 출처 | 쿼리 스트링 / form | HTTP 본문 |
| Content-Type | application/x-www-form-urlencoded |
application/json 등 |
| 적합한 HTTP 메서드 | 주로 GET | 주로 POST·PUT·PATCH |
| 매핑 대상 | 단일 값 또는 리스트 | 객체 전체 |
| GET 본문 | 거의 안 씀 | 안 씀 (REST 비표준) |
GET 요청에는 본문이 거의 없어요. 모든 데이터는 쿼리 스트링으로. @RequestBody 와 GET을 같이 쓰는 패턴은 거의 만나지 않습니다.
@RequestHeader·@CookieValue — 보조 둘
가끔 만나는 두 어노테이션.
@GetMapping("/profile")
public User profile(@RequestHeader("Authorization") String token,
@CookieValue(value = "SESSION", required = false) String session) {
return userService.findByToken(token);
}
@RequestHeader— HTTP 헤더 한 줄 추출@CookieValue— 쿠키 한 개 추출
@RequestHeader 는 Authorization 헤더에서 JWT 토큰 추출에 자주 등장. 단 실무에서는 Spring Security가 자동 처리하니까 직접 박는 일은 드물어요.
@ModelAttribute — form 데이터를 객체로
HTML form 제출(application/x-www-form-urlencoded)에서 form 필드들을 한 객체로 묶을 때 @ModelAttribute.
@PostMapping("/users")
public String register(@ModelAttribute UserRegisterForm form) {
userService.register(form);
return "redirect:/users";
}
@Getter @Setter
public class UserRegisterForm {
private String username;
private String email;
private String password;
}
@RequestBody 와 비슷해 보이지만 — Content-Type이 form-urlencoded일 때 동작. JSON은 @RequestBody, 전통 HTML 폼은 @ModelAttribute. REST API 시대엔 거의 안 쓰지만 "이런 게 있다" 만 알아두세요.
@Valid — 자동 검증
@RequestBody 와 가장 자주 짝짓는 어노테이션 @Valid.
@PostMapping("/orders")
public Order create(@RequestBody @Valid OrderRequest req) {
return orderService.create(req);
}
@Getter @Setter
@NoArgsConstructor
public class OrderRequest {
@NotNull
private Long productId;
@Min(1)
@Max(100)
private int quantity;
@Email
private String customerEmail;
}
@Valid 한 줄만 박으면 — Spring이 자동으로 DTO 필드의 @NotNull·@Min·@Email 같은 검증 어노테이션을 실행. 검증 실패 시 MethodArgumentNotValidException 자동 발생, 400 Bad Request 응답.
24·25편 Validation에서 깊이 다룰 내용. 미리 그림만 잡아두세요.
실전 예 — REST API 전체 메서드 시그니처
지금까지 다룬 어노테이션을 합쳐 "주문 검색 + 상세 + 생성 + 수정" 컨트롤러.
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
// 검색 — 쿼리 스트링
@GetMapping
public List<OrderResponse> search(
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) { ... }
// 상세 — Path Variable
@GetMapping("/{id}")
public OrderResponse detail(@PathVariable Long id) { ... }
// 생성 — JSON 본문 + 검증
@PostMapping
public OrderResponse create(
@RequestBody @Valid OrderCreateRequest req,
@RequestHeader("X-Tenant-Id") String tenantId) { ... }
// 수정 — Path + JSON 본문
@PatchMapping("/{id}")
public OrderResponse update(
@PathVariable Long id,
@RequestBody @Valid OrderUpdateRequest req) { ... }
}
이 네 메서드가 REST API 컨트롤러의 표준 골격이에요. 22편의 모든 어노테이션이 자연스럽게 합쳐진 형태.
리소스 식별(이것 = id) → @PathVariable. 옵션·필터(어떤 조건) → @RequestParam. 생성·수정 데이터(무엇을 만들/바꿀) → @RequestBody. 인증·메타(누가) → @RequestHeader.
한 줄 정리 — @PathVariable (URL 변수), @RequestParam (쿼리·form), @RequestBody (JSON 본문), @RequestHeader (헤더). 5개 + @Valid 검증 한 줄로 REST API의 모든 입력 처리.
시험 직전 한 번 더 — 요청 데이터 추출 입문자가 매번 헷갈리는 것
- 요청 데이터 추출 5종 =
@PathVariable/@RequestParam/@RequestBody/@RequestHeader/@CookieValue @PathVariable= URL 경로 안{...}(리소스 식별)- 매개변수 이름이 URL 변수와 같으면 자동 매핑
- 다르면
@PathVariable("id") Long orderId명시 @RequestParam= 쿼리 스트링 또는 form (선택적 옵션)- 옵션 3가지 =
value/required/defaultValue required = false+defaultValue패턴이 표준- 리스트로 받기 =
@RequestParam List<String> tags @RequestBody= HTTP 본문 (JSON·XML)- POST·PUT·PATCH 요청의 99% 패턴
- DTO에 기본 생성자 필수 — Jackson 데시리얼라이즈
- Lombok
@NoArgsConstructor박아두기 @RequestHeader= HTTP 헤더 추출@CookieValue= 쿠키 추출@ModelAttribute= form-urlencoded form 데이터를 객체로 (REST 시대엔 거의 X)@Valid한 줄 = DTO 필드 검증 자동 실행- 검증 실패 시
MethodArgumentNotValidException자동 → 400 - GET 요청에
@RequestBody거의 안 씀 - 타입 변환 자동 =
String "123"→Long 123L, 실패 시 400 - "리소스 식별 → PathVariable / 옵션 → RequestParam / 데이터 → RequestBody" 룰
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 24편 — @Aspect로 첫 AOP 박기
- 25편 — DispatcherServlet 요청 처리 흐름
- 26편 — Filter vs Interceptor 비교
- 27편 — @Controller @RequestMapping
- 28편 — @RestController와 JSON 응답
다음 글: