@RequestBody & @ResponseBody
Beginner@RequestBody deserialises the HTTP body into a Java object using Jackson; @ResponseBody serialises the return value back to JSON or XML.
Overview
@RequestBody binds the HTTP request body to a Java object using an HttpMessageConverter (Jackson for JSON by default). @ResponseBody writes the return value of a handler method directly to the HTTP response body, bypassing view resolution. In @RestController both are implicit on every method. Pair @RequestBody with @Valid or @Validated to trigger Bean Validation on the deserialised object. Understand how Jackson's ObjectMapper is configured — null handling, date formats, unknown property behaviour — to avoid surprise serialisation bugs.
@RequestBody with Validation
@RequestBody deserialises the incoming JSON/XML. Add @Valid to trigger JSR-303 validation immediately after deserialization. A MethodArgumentNotValidException is thrown on constraint violations.
// Request DTO with validation constraints
public record CreateOrderRequest(
@NotNull Long customerId,
@NotEmpty List<@Valid OrderLineRequest> lines,
@Size(max = 500) String notes
) {}
public record OrderLineRequest(
@NotBlank String sku,
@Min(1) @Max(100) int quantity
) {}
// Controller
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public OrderDTO create(@RequestBody @Valid CreateOrderRequest req) {
// If any @NotNull / @NotEmpty / @Min fails, Spring throws
// MethodArgumentNotValidException before this method body runs
return orderService.create(req);
}
}
// Handle validation errors globally
@RestControllerAdvice
public class ValidationHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
return ex.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage
));
}
}Jackson Configuration
Spring Boot auto-configures Jackson's ObjectMapper. Customise it via application.properties (spring.jackson.*) or by declaring a Jackson2ObjectMapperBuilderCustomizer bean.
# application.properties — Jackson ObjectMapper config
spring.jackson.serialization.indent-output=true
spring.jackson.deserialization.fail-on-unknown-properties=false # ignore extra JSON fields
spring.jackson.default-property-inclusion=non_null # skip null fields in output
spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.SSSZ
spring.jackson.time-zone=UTC
// Programmatic customisation
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> builder
.serializationInclusion(JsonInclude.Include.NON_NULL)
.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modules(new JavaTimeModule()) // LocalDateTime, ZonedDateTime etc.
.timeZone(TimeZone.getTimeZone("UTC"));
}
}
// DTO — custom serialisation with annotations
public class OrderDTO {
@JsonProperty("order_id") // rename field in JSON
private Long id;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime createdAt;
@JsonIgnore // exclude from JSON output
private String internalNote;
}Streaming Large Responses
For large payloads, avoid loading the entire response into memory. Return ResponseBodyEmitter or StreamingResponseBody to stream data directly to the client without buffering.
// Stream a large file download
@GetMapping("/api/exports/{id}")
public ResponseEntity<StreamingResponseBody> exportOrders(@PathVariable Long id) {
StreamingResponseBody body = outputStream -> {
try (var writer = new OutputStreamWriter(outputStream)) {
orderService.streamOrders(id, order ->
writer.write(objectMapper.writeValueAsString(order) + "\n")
);
}
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=orders.jsonl")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(body);
}
// Server-Sent Events (SSE) — push real-time updates to browser
@GetMapping(path = "/api/orders/{id}/status-stream",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<OrderStatus>> statusStream(@PathVariable Long id) {
return orderService.statusUpdates(id)
.map(status -> ServerSentEvent.<OrderStatus>builder()
.event("status-update")
.data(status)
.build());
}Key Points to Remember
- 1@RequestBody deserialises the HTTP body via HttpMessageConverter (Jackson for JSON).
- 2@ResponseBody writes the return value directly to the HTTP response — implicit in @RestController.
- 3Add @Valid to @RequestBody to trigger Bean Validation; MethodArgumentNotValidException on failure.
- 4spring.jackson.deserialization.fail-on-unknown-properties=false prevents errors on extra fields.
- 5Use JavaTimeModule to serialise/deserialise java.time types (LocalDateTime, ZonedDateTime).
- 6Return StreamingResponseBody or Flux for large or real-time responses without memory buffering.
Interview Questions
Sign in to ask AriaWhat is the difference between @RequestBody and @RequestParam?
How does Spring validate a @RequestBody object and what exception is thrown on failure?
How does Jackson handle an incoming JSON field that does not exist on the Java DTO?
How would you customise Jackson's ObjectMapper globally in Spring Boot?
How do you stream a large response without loading it all into memory?
Ask Aria about @RequestBody & @ResponseBody
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.