⚙️ Dynamic Request Bodies in Spring Boot — With One Clean, Reusable Endpoint
“Can we send a different JSON structure depending on the use case?”
That’s the moment every backend developer realizes — static DTOs don’t scale.
Instead of writing 10 different controllers, I’ll show you how to handle dynamic JSON bodies cleanly in one endpoint — with full validation and zero chaos.
💡 The Problem: When DTOs Become a Nightmare
In a perfect world, every request has a well-defined DTO:
public class UserRequest {
private String name;
private String email;
}But then product says:
“We’ll add admin registration, and each role has different fields.”
So you end up writing:
UserRequest
AdminRequest
GuestRequest
PartnerRequestAnd your controller starts looking like this 😵💫:
@PostMapping("/user") ...
@PostMapping("/admin") ...
@PostMapping("/guest") ...That’s rigid, repetitive, and impossible to maintain.
🚀 The Solution: Dynamic Request Handling
Instead of hardcoding DTOs, we’ll make Spring Boot accept dynamic JSON structures — and still keep type safety and validation.
Get Lakshika’s stories in your inbox
Join Medium for free to get updates from this writer.
We’ll use:
@RequestBody Map<String, Object>for flexible payloads- Optional: Jackson’s
JsonNodefor structured but dynamic parsing - Optional: Polymorphic DTOs for cleaner deserialization
🧩 Step 1: Accepting Flexible JSON with Map<String, Object>
@RestController
@RequestMapping("/api/register")
public class RegistrationController {
@PostMapping
public ResponseEntity<String> register(@RequestBody Map<String, Object> payload) {
String userType = (String) payload.get("type");
switch (userType) {
case "admin" -> handleAdmin(payload);
case "user" -> handleUser(payload);
default -> throw new IllegalArgumentException("Unknown type: " + userType);
}
return ResponseEntity.ok("Registered successfully ✅");
}
private void handleAdmin(Map<String, Object> payload) {
System.out.println("Admin payload: " + payload);
}
private void handleUser(Map<String, Object> payload) {
System.out.println("User payload: " + payload);
}
}🧠 Result:
Now you can send any JSON body:
{
"type": "admin",
"email": "root@example.com",
"permissions": ["READ", "WRITE"]
}or
{
"type": "user",
"name": "Alice",
"referralCode": "XYZ"
}…and it works with a single endpoint.
⚙️ Step 2: Using JsonNode for Structured Dynamic Access
For better control, use Jackson’s JsonNode:
@PostMapping("/json")
public ResponseEntity<String> registerWithJson(@RequestBody JsonNode node) {
String type = node.path("type").asText();
if ("admin".equals(type)) {
JsonNode permissions = node.path("permissions");
System.out.println("Admin permissions: " + permissions);
}
return ResponseEntity.ok("Processed " + type);
}💡 Why it’s great:
- You get type-safe access (
asText(),asInt(), etc.) - No need for predefined POJOs
- Perfect for third-party webhooks or configurable forms
🧠 Step 3: Polymorphic DTOs — Type Safety + Flexibility
For cleaner code and validation, define type-based DTOs with Jackson polymorphism.
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = AdminRequest.class, name = "admin"),
@JsonSubTypes.Type(value = UserRequest.class, name = "user")
})
public abstract class BaseRequest {
public String type;
}
@Data
class AdminRequest extends BaseRequest {
private List<String> permissions;
}
@Data
class UserRequest extends BaseRequest {
private String name;
private String referralCode;
}Then your controller can stay clean:
@PostMapping("/dynamic")
public ResponseEntity<String> register(@RequestBody BaseRequest request) {
if (request instanceof AdminRequest admin) {
System.out.println("Admin: " + admin.getPermissions());
} else if (request instanceof UserRequest user) {
System.out.println("User: " + user.getName());
}
return ResponseEntity.ok("Processed " + request.getType());
}✅ One endpoint
✅ Type-safe polymorphism
✅ Automatic deserialization
🧩 Step 4: Add Runtime Validation
Use @Valid + @JsonTypeInfo for runtime validation:
@PostMapping("/validate")
public ResponseEntity<String> registerValidated(@Valid @RequestBody BaseRequest request) {
return ResponseEntity.ok("Valid request for type: " + request.getType());
}You can annotate subclasses normally:
@Data
class UserRequest extends BaseRequest {
@NotBlank
private String name;
}If the request is invalid, Spring Boot automatically returns a 400 Bad Request with validation errors. 💥
🧠 Step 5: Bonus — Dynamic Fields in Entity Mapping
If you store flexible JSON in your DB (like PostgreSQL jsonb), map it like this:
@Entity
@Table(name = "dynamic_forms")
public class DynamicForm {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String type;
@Column(columnDefinition = "jsonb")
@Convert(converter = JsonNodeConverter.class)
private JsonNode data;
}With a converter:
@Converter
public class JsonNodeConverter implements AttributeConverter<JsonNode, String> {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(JsonNode attribute) {
try {
return mapper.writeValueAsString(attribute);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public JsonNode convertToEntityAttribute(String dbData) {
try {
return mapper.readTree(dbData);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}Now you can save any flexible JSON dynamically!
📊 Performance Tip
If you use dynamic payloads heavily:
- Use JSONB indexes (PostgreSQL)
- Cache parsed nodes to avoid repeated deserialization
- Validate schema at API gateway layer for better control
🧠 TL;DR
💬 Final Thoughts
Dynamic request bodies don’t have to mean chaotic code.
With Spring Boot’s flexible deserialization and a bit of Jackson magic, you can build smart, scalable APIs that handle multiple payloads — without creating a DTO jungle 🌴.
So next time your frontend team says:
“We might send a few different fields…”
You just smile and say:
“Send whatever you want — I’ve got it covered 😉.”
A message from our Founder
Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.
Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don’t receive any funding, we do this to support the community. ❤️
If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, Instagram. You can also subscribe to our weekly newsletter.
And before you go, don’t forget to clap and follow the writer️!











