4 Common @RequestBody Pitfalls That Can Crash Your Spring Apps
The article explains how @RequestBody binds JSON via HttpMessageConverter using Jackson, then details four frequent issues—empty bodies, extra or unknown fields, date‑format mismatches, and deep nesting causing circular serialization—and provides concrete code‑level solutions such as validation, global exception handling, Jackson configuration, and DTO usage.
What @RequestBody Actually Does
The core role of @RequestBody is to read the HTTP request body (JSON, XML, etc.) and convert it into a Java object through HttpMessageConverter . Spring Boot uses Jackson as the default JSON serializer/deserializer.
The prerequisite for @RequestBody is that the request body must be JSON (or XML) and the Content-Type header must be application/json (or application/xml ). For form-data or x-www-form-urlencoded , use @RequestParam or @ModelAttribute instead.
1. Pitfall: Empty Request Body
@PostMapping("/order")
public Result createOrder(@RequestBody Order order) {
// ❌ Empty body leads to 400 Bad Request or null order
if (order == null) {
// handle null if framework allows it
}
orderService.create(order);
return Result.success();
}When the front‑end sends an empty body, Spring throws HttpMessageNotReadableException (400). If the framework is configured to allow null, the method receives null and subsequent calls cause NPE.
Solution:
Include the validation starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>Add validation annotations on the DTO and use @Valid:
@Data
public class Order {
@NotNull
private Long id;
@NotBlank
private String name;
// ...
}
@PostMapping("/order")
public Result createOrder(@RequestBody @Valid Order order) {
orderService.create(order);
return Result.success();
}Define a global exception handler:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidation(MethodArgumentNotValidException e) {
return Result.error("Parameter validation failed: " + e.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result handleNotReadable() {
return Result.error("Request body cannot be empty or malformed");
}
}2. Pitfall: Extra/Unknown Fields
If the front‑end sends fields that do not exist in the target class, Jackson by default throws UnrecognizedPropertyException. Spring Boot usually disables this behavior, but if the configuration is left enabled the request fails with 400.
Solution 1 – Global ignore:
spring:
jackson:
deserialization:
fail-on-unknown-properties: falseSolution 2 – Annotation on the entity:
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class Order {
private Long id;
private String name;
private BigDecimal amount;
}3. Pitfall: Date‑Format Parsing Failure
Jackson only recognises ISO‑8601 format for LocalDateTime. A request like {"createTime":"2026-07-06 14:30:00"} triggers InvalidFormatException.
Solution 1 – Field annotation:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;Solution 2 – Global JavaTimeModule registration:
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
module.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.modules(module);
};
}
}For a few fields, @JsonFormat is the quickest; for many fields, register JavaTimeModule globally.
4. Pitfall: Deep Nesting Causing Circular Serialization
@Data
public class Order {
private Long id;
private User user; // nested
private List<OrderItem> items; // nested collection
private Payment payment; // nested
private Address address; // nested
}
@Data
public class User {
private Long id;
private String name;
private List<Order> orders; // circular reference!
}During response serialization, Jackson would recurse infinitely, leading to stack overflow or huge payloads.
Solution 1 – Ignore the back‑reference:
@JsonIgnore
private List<Order> orders; // ignored in both serialization and deserializationSolution 2 – Use DTO/VO instead of exposing the entity:
@Data
public class OrderVO {
private Long id;
private Long userId; // only the ID, not the full User object
private List<OrderItemVO> items;
}Solution 3 – Explicitly mark the relationship:
// User.java
@JsonManagedReference
private List<Order> orders;
// Order.java
@JsonBackReference
private User user;The most robust approach is to keep entities internal and expose DTOs at the API layer.
Summary
@RequestBody works via HttpMessageConverter with Jackson as the default mapper.
Empty bodies → 400 / NPE; handle with global exception handling and @Valid field checks.
Unknown fields → configure fail-on-unknown-properties: false or use @JsonIgnoreProperties.
Date format issues → @JsonFormat or register JavaTimeModule globally.
Deep nesting → avoid exposing entities; use DTO/VO, @JsonIgnore, or @JsonManagedReference/@JsonBackReference.
Always ensure the request’s Content-Type matches the body format (typically application/json).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
