Skip to main content

Week 4 Day 3 — Request and Response Bodies

Goal

Today I want to understand how Spring MVC handles request bodies and response bodies.

Main questions:

  1. What is an HTTP request body?
  2. What is an HTTP response body?
  3. What does @RequestBody do?
  4. What does @ResponseBody do?
  5. Why does @RestController return JSON automatically?
  6. What is HttpMessageConverter?
  7. What is Jackson?
  8. What are DTOs?
  9. Why should REST APIs usually return DTOs, not entities?
  10. How does validation work with @Valid?
  11. What is ResponseEntity?
  12. What are common exam traps?

1. Quick Review from Week 4 Day 2

In Day 2, I learned:

  • @RequestMapping maps HTTP requests to controller methods.
  • @GetMapping, @PostMapping, @PutMapping, @PatchMapping, and @DeleteMapping are shortcut annotations.
  • @PathVariable reads path values.
  • @RequestParam reads query parameters.
  • @RequestHeader reads headers.
  • @RequestBody reads the HTTP body.
  • consumes checks request Content-Type.
  • produces describes response media type.
  • Content-Type means request body format.
  • Accept means desired response format.

Memory sentence:

PathVariable = path.
RequestParam = query.
RequestHeader = header.
RequestBody = body.

Today I go deeper into request and response bodies.


2. What Is an HTTP Request Body?

The request body is the data sent by the client to the server.

Example:

POST /api/tasks
Content-Type: application/json

{
"title": "Learn Spring MVC",
"priority": "HIGH"
}

The body is:

{
"title": "Learn Spring MVC",
"priority": "HIGH"
}

The body is usually used with:

POST
PUT
PATCH

Common request body formats:

JSON
XML
form data
multipart file upload
plain text

For REST APIs, JSON is most common.


3. What Is an HTTP Response Body?

The response body is the data sent by the server back to the client.

Example response:

HTTP/1.1 201 Created
Content-Type: application/json

{
"id": 1,
"title": "Learn Spring MVC",
"priority": "HIGH"
}

The response body is:

{
"id": 1,
"title": "Learn Spring MVC",
"priority": "HIGH"
}

Memory sentence:

Request body comes from the client. Response body goes back to the client.


4. @RequestBody

@RequestBody tells Spring:

Read the HTTP request body and convert it into a Java object.

Request:

POST /api/tasks
Content-Type: application/json

{
"title": "Learn Spring MVC"
}

DTO:

public record CreateTaskRequest(String title) {
}

Controller:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Spring converts JSON into:

CreateTaskRequest

Memory sentence:

@RequestBody means JSON body to Java object.


5. What Happens Internally with @RequestBody?

Flow:

1. Client sends JSON body.
2. Spring MVC receives request.
3. Controller method has @RequestBody.
4. Spring checks Content-Type.
5. Spring finds suitable HttpMessageConverter.
6. Jackson converts JSON into Java object.
7. Controller method receives the Java object.

Example:

{
"title": "Learn Spring"
}

becomes:

new CreateTaskRequest("Learn Spring")

With Java records, Spring/Jackson can create the record from JSON fields.


6. @RequestBody and Content-Type

The client should send:

Content-Type: application/json

This tells Spring:

The request body is JSON.

Then Spring can choose the JSON converter.

If the endpoint expects JSON but the client sends:

Content-Type: text/plain

Spring may return:

415 Unsupported Media Type

Memory sentence:

Content-Type tells Spring how to read the request body.


7. Invalid JSON Body

Request:

POST /api/tasks
Content-Type: application/json

{
"title":
}

This JSON is invalid.

Spring cannot convert it into a Java object.

Usually response:

400 Bad Request

Why?

Because the client sent invalid request data.


8. Missing @RequestBody

Problem:

@PostMapping("/api/tasks")
public TaskDto create(CreateTaskRequest request) {
return taskService.create(request);
}

Request:

POST /api/tasks
Content-Type: application/json

{
"title": "Learn Spring"
}

Without @RequestBody, Spring may not read JSON from the body into CreateTaskRequest.

Fix:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Memory sentence:

For JSON request body, use @RequestBody.


9. One Request Body

Usually, one controller method should have one @RequestBody.

Bad:

@PostMapping("/api/tasks")
public TaskDto create(
@RequestBody CreateTaskRequest request,
@RequestBody AuditInfo auditInfo
) {
return taskService.create(request, auditInfo);
}

Why bad?

One HTTP request has one body.
Spring cannot normally split one body into two separate @RequestBody objects.

Better:

public record CreateTaskCommand(
String title,
AuditInfo auditInfo
) {
}

Controller:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskCommand command) {
return taskService.create(command);
}

Memory sentence:

One HTTP request body should map to one request object.


10. @ResponseBody

@ResponseBody tells Spring:

Write the method return value directly to the HTTP response body.

Example:

@Controller
public class HelloController {

@GetMapping("/api/hello")
@ResponseBody
public String hello() {
return "hello";
}
}

Response body:

hello

Without @ResponseBody, Spring may treat "hello" as a view name.


11. @RestController Includes @ResponseBody

Very important exam sentence:

@RestController = @Controller + @ResponseBody

This:

@RestController
public class HelloController {

@GetMapping("/api/hello")
public String hello() {
return "hello";
}
}

means the return value goes directly into the response body.

So response body is:

hello

Spring does not look for a view named hello.


12. Response Body with Java Object

Controller:

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping("/{id}")
public TaskDto get(@PathVariable Long id) {
return new TaskDto(id, "Learn Spring MVC");
}
}

DTO:

public record TaskDto(Long id, String title) {
}

Response:

{
"id": 1,
"title": "Learn Spring MVC"
}

Spring converts the Java object to JSON automatically.


13. Who Converts Java Objects to JSON?

The converter is:

HttpMessageConverter

For JSON, Spring Boot usually uses:

Jackson

Flow:

Java object -> HttpMessageConverter -> JSON response body

Memory sentence:

HttpMessageConverter converts between HTTP bodies and Java objects.


14. HttpMessageConverter

HttpMessageConverter converts in two directions:

Request body -> Java object
Java object -> Response body

Examples:

JSON -> Java object
Java object -> JSON
String -> text/plain
byte[] -> binary response
form data -> Java object or parameters

For a REST API:

JSON conversion is the most important.

15. Jackson

Jackson is a Java library for JSON serialization and deserialization.

Serialization means:

Java object -> JSON

Deserialization means:

JSON -> Java object

Example serialization:

new TaskDto(1L, "Learn Spring")

becomes:

{
"id": 1,
"title": "Learn Spring"
}

Example deserialization:

{
"title": "Learn Spring"
}

becomes:

CreateTaskRequest

Memory sentence:

Jackson reads JSON into Java and writes Java into JSON.


16. Spring Boot and Jackson

When I add:

implementation("org.springframework.boot:spring-boot-starter-web")

Spring Boot usually brings and configures Jackson automatically.

So this works without manual ObjectMapper setup:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Spring Boot auto-configures JSON conversion.


17. DTOs

DTO means:

Data Transfer Object

Simple definition:

A DTO is an object used to transfer data between layers or over the network.

In REST APIs, DTOs are used for request and response bodies.

Request DTO:

public record CreateTaskRequest(
String title,
String priority
) {
}

Response DTO:

public record TaskDto(
Long id,
String title,
String priority,
String status
) {
}

Memory sentence:

DTOs define the API contract.


18. Request DTO vs Response DTO

Request DTO describes what the client sends.

public record CreateTaskRequest(
String title,
String priority
) {
}

Response DTO describes what the server returns.

public record TaskDto(
Long id,
String title,
String priority,
String status
) {
}

They do not need to be the same.

Example:

Client sends:

{
"title": "Learn Spring",
"priority": "HIGH"
}

Server returns:

{
"id": 100,
"title": "Learn Spring",
"priority": "HIGH",
"status": "OPEN"
}

19. Why Use DTOs?

DTOs help with:

stable API contracts
hiding internal database structure
avoiding sensitive field leaks
avoiding lazy loading serialization problems
controlling response shape
separating API from persistence model
validation
versioning

Memory sentence:

DTOs protect the API boundary.


20. Entity vs DTO

Entity example:

@Entity
public class UserEntity {

@Id
private Long id;

private String email;

private String passwordHash;

private boolean internalAdminFlag;
}

Bad controller:

@GetMapping("/api/users/{id}")
public UserEntity getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow();
}

Problem:

passwordHash may be exposed
internal fields may leak
database model becomes API contract
lazy relationships may break serialization

Better response DTO:

public record UserDto(
Long id,
String email
) {
}

Controller:

@GetMapping("/api/users/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.findById(id);
}

Memory sentence:

Do not expose your database entity as your public API.


21. Mapping Entity to DTO

Entity:

public class TaskEntity {

private Long id;
private String title;
private String status;

public Long getId() {
return id;
}

public String getTitle() {
return title;
}

public String getStatus() {
return status;
}
}

DTO:

public record TaskDto(
Long id,
String title,
String status
) {
}

Manual mapping:

public TaskDto toDto(TaskEntity entity) {
return new TaskDto(
entity.getId(),
entity.getTitle(),
entity.getStatus()
);
}

For the exam, manual mapping is enough to understand.

In real projects, some teams use mapping libraries, but manual mapping is often clear and safe.


22. Request DTO Should Not Be Entity

Bad:

@PostMapping("/api/tasks")
public TaskEntity create(@RequestBody TaskEntity taskEntity) {
return taskRepository.save(taskEntity);
}

Why bad?

client can send fields it should not control
entity structure leaks into API
validation becomes unclear
security risk with internal fields

Better:

public record CreateTaskRequest(
String title,
String priority
) {
}

Controller:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

23. Validation

Validation checks whether request data is valid.

Example rules:

title must not be blank
email must be valid
age must be at least 18
price must be positive
date must be in the future
list must not be empty

In Spring Boot, validation usually uses Bean Validation annotations.

Need dependency:

implementation("org.springframework.boot:spring-boot-starter-validation")

Memory sentence:

Use validation to reject bad client input early.


24. Common Validation Annotations

Examples:

@NotNull
@NotBlank
@NotEmpty
@Size
@Min
@Max
@Positive
@PositiveOrZero
@Email
@Pattern
@Past
@Future

Common meaning:

AnnotationMeaning
@NotNullvalue must not be null
@NotBlankstring must contain non-whitespace text
@NotEmptycollection/string must not be empty
@Sizesize must be within range
@Minnumber must be at least value
@Maxnumber must be at most value
@Emailstring must be email format
@Positivenumber must be greater than zero

25. @NotNull vs @NotBlank vs @NotEmpty

This is an exam trap.

@NotNull

Value cannot be null.

@NotNull
private String name;

Invalid:

null

But this can be valid:

""
" "

@NotEmpty

Value cannot be null or empty.

Invalid:

null
""
empty list

But this can be valid for String:

" "

@NotBlank

For strings.

Invalid:

null
""
" "

Memory sentence:

For required text fields, use @NotBlank.


26. Validation Example

Request DTO:

public record CreateTaskRequest(

@NotBlank
@Size(max = 100)
String title,

@NotBlank
String priority
) {
}

Controller:

@PostMapping("/api/tasks")
public ResponseEntity<TaskDto> create(
@Valid @RequestBody CreateTaskRequest request
) {
TaskDto created = taskService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

Important:

Validation runs because of @Valid.

Without @Valid, the annotations may not be checked in the controller method.

Memory sentence:

Validation annotations define rules. @Valid triggers validation.


27. What Happens When Validation Fails?

Request:

POST /api/tasks
Content-Type: application/json

{
"title": "",
"priority": "HIGH"
}

DTO:

public record CreateTaskRequest(
@NotBlank String title,
@NotBlank String priority
) {
}

Controller:

@PostMapping("/api/tasks")
public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Because title is blank, validation fails.

Usually response:

400 Bad Request

Spring throws a validation-related exception.

Later we will learn how to customize validation error responses with @ControllerAdvice.


28. Nested Validation

Request DTO:

public record CreateClientRequest(

@NotBlank
String name,

@Valid
AddressRequest address
) {
}

Nested DTO:

public record AddressRequest(

@NotBlank
String street,

@NotBlank
String city
) {
}

Important:

Use @Valid on the nested field to validate nested object.

Without nested @Valid, nested validation may not run.

Memory sentence:

Use @Valid for nested objects too.


29. Validation on Lists

Example:

public record CreateOrderRequest(

@NotEmpty
List<@Valid OrderItemRequest> items
) {
}

Nested item:

public record OrderItemRequest(

@NotNull
Long productId,

@Positive
int quantity
) {
}

Meaning:

items list must not be empty
each item must be valid
quantity must be positive

30. @Validated

@Valid is most common for request body validation.

@Validated is a Spring annotation that can also trigger validation and supports validation groups.

Example:

@RestController
@Validated
public class TaskController {

@GetMapping("/api/tasks")
public List<TaskDto> list(@RequestParam @Min(0) int page) {
return taskService.findPage(page);
}
}

For many controller request bodies:

@Valid @RequestBody CreateTaskRequest request

is enough.

Exam-safe sentence:

Use @Valid with @RequestBody to validate request DTOs. @Validated is Spring’s variant and supports validation groups.


31. Validation on Query Parameters

Example:

@RestController
@Validated
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping
public List<TaskDto> list(
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size
) {
return taskService.findPage(page, size);
}
}

Important:

For method parameter validation, @Validated on the controller class is commonly used.

32. ResponseEntity

ResponseEntity represents the full HTTP response.

It can control:

status code
headers
body

Example:

@PostMapping("/api/tasks")
public ResponseEntity<TaskDto> create(@Valid @RequestBody CreateTaskRequest request) {
TaskDto created = taskService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

Response:

HTTP/1.1 201 Created
Content-Type: application/json

{
"id": 1,
"title": "Learn Spring"
}

Memory sentence:

ResponseEntity gives control over status, headers, and body.


33. Returning 200 OK

@GetMapping("/api/tasks/{id}")
public ResponseEntity<TaskDto> get(@PathVariable Long id) {
TaskDto task = taskService.findById(id);
return ResponseEntity.ok(task);
}

Meaning:

HTTP 200 OK with body

34. Returning 201 Created

@PostMapping("/api/tasks")
public ResponseEntity<TaskDto> create(@Valid @RequestBody CreateTaskRequest request) {
TaskDto created = taskService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

Meaning:

HTTP 201 Created with body

For create operations, 201 Created is usually better than 200 OK.


35. Returning Location Header

For newly created resources, I can return a Location header.

@PostMapping("/api/tasks")
public ResponseEntity<TaskDto> create(@Valid @RequestBody CreateTaskRequest request) {
TaskDto created = taskService.create(request);

URI location = URI.create("/api/tasks/" + created.id());

return ResponseEntity
.created(location)
.body(created);
}

Response:

HTTP/1.1 201 Created
Location: /api/tasks/1
Content-Type: application/json

Memory sentence:

ResponseEntity.created(location) returns 201 with a Location header.


36. Returning 204 No Content

Delete endpoint:

@DeleteMapping("/api/tasks/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
taskService.delete(id);
return ResponseEntity.noContent().build();
}

Meaning:

HTTP 204 No Content

Important:

204 response should not have a response body.

37. Returning 404 Not Found

Simple example:

@GetMapping("/api/tasks/{id}")
public ResponseEntity<TaskDto> get(@PathVariable Long id) {
Optional<TaskDto> task = taskService.findById(id);

return task
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

Meaning:

If task exists -> 200 OK with body
If task does not exist -> 404 Not Found

In larger apps, this is often handled with exceptions and @ControllerAdvice.


38. @ResponseStatus

For fixed status codes, I can use @ResponseStatus.

Example:

@PostMapping("/api/tasks")
@ResponseStatus(HttpStatus.CREATED)
public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

This returns:

201 Created

Use @ResponseStatus when status is fixed.

Use ResponseEntity when status, headers, or body need more control.


39. ResponseEntity vs @ResponseStatus

TopicResponseEntity@ResponseStatus
Statusdynamic or fixedfixed
Headerseasy to controlnot ideal
Bodyexplicitmethod return value
Common useflexible responsessimple fixed status
Example200 or 404always 201

Memory sentence:

Use ResponseEntity for control. Use @ResponseStatus for simple fixed status.


40. Full Create Flow with Validation

Request:

POST /api/tasks
Content-Type: application/json
Accept: application/json

{
"title": "Learn validation",
"priority": "HIGH"
}

Request DTO:

public record CreateTaskRequest(

@NotBlank
@Size(max = 100)
String title,

@NotBlank
String priority
) {
}

Controller:

@PostMapping(
value = "/api/tasks",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<TaskDto> create(
@Valid @RequestBody CreateTaskRequest request
) {
TaskDto created = taskService.create(request);

URI location = URI.create("/api/tasks/" + created.id());

return ResponseEntity.created(location).body(created);
}

Flow:

1. Client sends JSON.
2. Spring checks Content-Type.
3. Jackson converts JSON to CreateTaskRequest.
4. Bean Validation checks @NotBlank and @Size.
5. Controller method runs.
6. Service creates task.
7. Controller returns ResponseEntity.
8. Jackson converts TaskDto to JSON.
9. Response status is 201 Created.
10. Location header points to new resource.

41. Common Error: Missing Validation Starter

Problem:

public record CreateTaskRequest(
@NotBlank String title
) {
}

But annotation is not found or validation does not work.

Possible missing dependency:

implementation("org.springframework.boot:spring-boot-starter-validation")

Memory sentence:

For Bean Validation in Spring Boot, add validation starter.


42. Common Error: Missing @Valid

Problem:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

DTO:

public record CreateTaskRequest(
@NotBlank String title
) {
}

The annotation exists, but validation may not run.

Fix:

@PostMapping("/api/tasks")
public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Memory sentence:

Add @Valid to trigger request body validation.


43. Common Error: Returning Entity with Lazy Relations

Entity:

@Entity
public class ClientEntity {

@OneToMany(mappedBy = "client")
private List<TaskEntity> tasks;
}

Controller:

@GetMapping("/api/clients/{id}")
public ClientEntity getClient(@PathVariable Long id) {
return clientRepository.findById(id).orElseThrow();
}

Possible problems:

lazy loading exception
infinite recursion
huge response body
sensitive fields exposed
API depends on database model

Better:

public record ClientDto(
Long id,
String name
) {
}

Return ClientDto.


44. Common Error: Infinite JSON Recursion

Example:

public class Client {
private List<Task> tasks;
}
public class Task {
private Client client;
}

If both sides are serialized:

Client -> tasks -> task -> client -> tasks -> task -> ...

This can cause infinite recursion.

DTOs solve this cleanly by controlling the response shape.

Memory sentence:

DTOs prevent many JSON serialization problems.


45. Common Error: Wrong Field Names

JSON:

{
"task_title": "Learn Spring"
}

Java record:

public record CreateTaskRequest(
String title
) {
}

Jackson expects:

{
"title": "Learn Spring"
}

If the JSON field name differs, the value may not bind as expected.

Possible fix:

public record CreateTaskRequest(
@JsonProperty("task_title")
String title
) {
}

Then JSON task_title maps to Java title.


46. Common Jackson Annotations

Useful annotations:

@JsonProperty
@JsonIgnore
@JsonFormat
@JsonInclude

Examples:

public record UserDto(

Long id,

@JsonProperty("email_address")
String email
) {
}
public class InternalUserDto {

private Long id;

@JsonIgnore
private String internalToken;
}

For certification, know the idea:

Jackson annotations can customize JSON serialization and deserialization.


47. Dates and JSON

DTO:

public record TaskDto(
Long id,
String title,
LocalDate dueDate
) {
}

JSON may look like:

{
"id": 1,
"title": "Learn Spring",
"dueDate": "2026-07-07"
}

For date/time formatting, use standard ISO formats when possible.

Custom format example:

public record TaskDto(

Long id,

String title,

@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate dueDate
) {
}

48. Request Body and Immutability

Java records are good DTOs because they are:

compact
immutable
clear
easy to serialize/deserialize
good for request/response data

Example:

public record CreateTaskRequest(
@NotBlank String title,
@NotBlank String priority
) {
}

For many modern Spring Boot apps, records are a clean DTO style.


49. Good REST Controller Example

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

private final TaskService taskService;

public TaskController(TaskService taskService) {
this.taskService = taskService;
}

@GetMapping("/{id}")
public ResponseEntity<TaskDto> get(@PathVariable Long id) {
return taskService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<TaskDto> create(
@Valid @RequestBody CreateTaskRequest request
) {
TaskDto created = taskService.create(request);
URI location = URI.create("/api/tasks/" + created.id());

return ResponseEntity.created(location).body(created);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
taskService.delete(id);
return ResponseEntity.noContent().build();
}
}

Request DTO:

public record CreateTaskRequest(

@NotBlank
@Size(max = 100)
String title,

@NotBlank
String priority
) {
}

Response DTO:

public record TaskDto(
Long id,
String title,
String priority,
String status
) {
}

50. Common HTTP Status Codes with Bodies

SituationStatus
Successful GET with body200 OK
Successful create201 Created
Successful delete without body204 No Content
Validation error400 Bad Request
Invalid JSON400 Bad Request
Resource not found404 Not Found
Unsupported request body type415 Unsupported Media Type
Unsupported response type requested406 Not Acceptable

51. Common Exam Traps

Trap 1

@RequestBody reads the HTTP request body.

It does not read query parameters.


Trap 2

@ResponseBody writes the return value to the HTTP response body.

It does not render a view.


Trap 3

@RestController includes @ResponseBody.


Trap 4

HttpMessageConverter converts between HTTP bodies and Java objects.


Trap 5

Jackson usually handles JSON in Spring Boot MVC apps.


Trap 6

Content-Type describes the request body format.

Accept describes the response format the client wants.


Trap 7

Validation annotations alone are not enough.

Use @Valid.


Trap 8

For Bean Validation support, add spring-boot-starter-validation.


Trap 9

DTOs are better than exposing JPA entities directly.


Trap 10

Use ResponseEntity to control status, headers, and body.


52. Real Exam Question: @RequestBody

Question:

What does @RequestBody do?

Answer:

@RequestBody tells Spring to read the HTTP request body and convert it into a Java object using an HttpMessageConverter.


53. Real Exam Question: @ResponseBody

Question:

What does @ResponseBody do?

Answer:

@ResponseBody tells Spring to write the method return value directly to the HTTP response body instead of treating it as a view name.


54. Real Exam Question: @RestController

Question:

What is @RestController equivalent to?

Answer:

@RestController is equivalent to @Controller plus @ResponseBody.


55. Real Exam Question: HttpMessageConverter

Question:

What does HttpMessageConverter do?

Answer:

It converts between HTTP request/response bodies and Java objects, such as JSON to Java object and Java object to JSON.


56. Real Exam Question: Jackson

Question:

What is Jackson used for in Spring MVC REST APIs?

Answer:

Jackson is usually used to serialize Java objects to JSON and deserialize JSON into Java objects.


57. Real Exam Question: DTO

Question:

What is a DTO?

Answer:

A DTO, or Data Transfer Object, is an object used to transfer data, especially across API boundaries. In REST APIs, request DTOs describe what clients send, and response DTOs describe what the server returns.


58. Real Exam Question: Entity vs DTO

Question:

Why should REST APIs usually return DTOs instead of JPA entities?

Answer:

DTOs protect the API boundary, avoid exposing internal database structure, prevent sensitive field leaks, avoid lazy loading and infinite recursion problems, and allow the API contract to evolve separately from the persistence model.


59. Real Exam Question: Validation

Question:

How do I validate a JSON request body?

Answer:

Add validation annotations to the request DTO, add spring-boot-starter-validation, and use @Valid with @RequestBody.

Example:

public record CreateTaskRequest(
@NotBlank String title
) {
}

@PostMapping("/api/tasks")
public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

60. Real Exam Question: ResponseEntity

Question:

Why use ResponseEntity?

Answer:

ResponseEntity allows a controller method to control the HTTP status code, response headers, and response body.


61. Real Exam Question: 201 Created

Question:

How can I return 201 Created with a body?

Answer:

return ResponseEntity.status(HttpStatus.CREATED).body(createdDto);

or with location:

return ResponseEntity.created(location).body(createdDto);

62. Real Exam Question: 204 No Content

Question:

How can I return 204 No Content?

Answer:

return ResponseEntity.noContent().build();

63. Interview Answer

Question:

How does Spring convert a JSON request body into a Java object?

Good answer:

When a controller method parameter is annotated with @RequestBody, Spring reads the HTTP request body and uses an HttpMessageConverter to convert it into the target Java type. In a typical Spring Boot MVC application, Jackson is auto-configured and used as the JSON converter. The request Content-Type, such as application/json, helps Spring choose the correct converter.


64. Interview Answer

Question:

How does Spring convert a Java object into a JSON response?

Good answer:

In a REST controller, the method return value is written to the response body because @RestController includes @ResponseBody. Spring then uses an HttpMessageConverter, usually Jackson, to serialize the Java object into JSON. The response format can also be influenced by content negotiation and the Accept header.


65. Interview Answer

Question:

Why do you use DTOs in REST APIs?

Good answer:

I use DTOs to define a clear API contract and to separate the external API from the internal persistence model. DTOs help avoid exposing sensitive fields, prevent lazy loading and infinite recursion issues, control the response shape, and make it easier to evolve the API without changing database entities directly.


66. Interview Answer

Question:

How do you validate request bodies in Spring Boot?

Good answer:

I add spring-boot-starter-validation, put Bean Validation annotations such as @NotBlank, @Size, or @Email on the request DTO, and annotate the controller parameter with @Valid @RequestBody. If validation fails, Spring usually returns a 400 Bad Request. For custom error responses, I can use @ControllerAdvice, which we will study later.


67. Interview Answer

Question:

What is ResponseEntity used for?

Good answer:

ResponseEntity represents the full HTTP response. It lets me control the status code, headers, and body. For example, I can return 201 Created with a Location header after creating a resource, return 204 No Content after deleting something, or return 404 Not Found when a resource does not exist.


68. Tiny Code Practice

Create request and response DTOs:

public record CreateTaskRequest(

@NotBlank
@Size(max = 100)
String title,

@NotBlank
String priority
) {
}

public record TaskDto(
Long id,
String title,
String priority,
String status
) {
}

Create controller:

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@PostMapping
public ResponseEntity<TaskDto> create(
@Valid @RequestBody CreateTaskRequest request
) {
TaskDto created = new TaskDto(
1L,
request.title(),
request.priority(),
"OPEN"
);

URI location = URI.create("/api/tasks/" + created.id());

return ResponseEntity.created(location).body(created);
}
}

Questions:

  1. What does @RequestBody do?
  2. What does @Valid do?
  3. What status code is returned?
  4. What header is returned?
  5. Who converts TaskDto to JSON?

Answers:

  1. It reads the JSON request body into CreateTaskRequest.
  2. It triggers validation.
  3. 201 Created.
  4. Location: /api/tasks/1.
  5. HttpMessageConverter, usually Jackson.

69. Tiny Bug Practice 1

Problem:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

DTO:

public record CreateTaskRequest(
@NotBlank String title
) {
}

Request:

{
"title": ""
}

Validation does not happen.

Question:

What is missing?

Answer:

@Valid is missing.

Fix:

@PostMapping("/api/tasks")
public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

70. Tiny Bug Practice 2

Problem:

@GetMapping("/api/users/{id}")
public UserEntity getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow();
}

Question:

Why is this risky?

Answer:

Returning a JPA entity directly can expose internal fields, leak sensitive data, cause lazy loading problems, cause infinite JSON recursion, and tightly couple the API to the database model. Return a DTO instead.


71. Tiny Bug Practice 3

Problem:

@Controller
@RequestMapping("/api/hello")
public class HelloController {

@GetMapping
public String hello() {
return "hello";
}
}

Expected response body:

hello

But Spring tries to resolve a view.

Question:

Why?

Answer:

@Controller does not automatically include @ResponseBody. Spring treats the returned String as a view name. Use @RestController or add @ResponseBody.


Practice Questions and Answers

Question 1

What is an HTTP request body?

Answer:

The HTTP request body is the data sent by the client to the server, often JSON for POST, PUT, or PATCH requests.


Question 2

What is an HTTP response body?

Answer:

The HTTP response body is the data sent by the server back to the client, often JSON in REST APIs.


Question 3

What does @RequestBody do?

Answer:

@RequestBody tells Spring to read the HTTP request body and convert it into a Java object using an HttpMessageConverter.


Question 4

What does @ResponseBody do?

Answer:

@ResponseBody tells Spring to write the method return value directly to the HTTP response body instead of treating it as a view name.


Question 5

What does @RestController include?

Answer:

@RestController includes @Controller and @ResponseBody.


Question 6

What is HttpMessageConverter?

Answer:

HttpMessageConverter converts between HTTP request/response bodies and Java objects.


Question 7

What is Jackson?

Answer:

Jackson is a JSON library used by Spring Boot to convert Java objects to JSON and JSON to Java objects.


Question 8

What is serialization?

Answer:

Serialization means converting a Java object into JSON or another external format.


Question 9

What is deserialization?

Answer:

Deserialization means converting JSON or another external format into a Java object.


Question 10

What is a DTO?

Answer:

A DTO is a Data Transfer Object used to transfer data across layers or API boundaries.


Question 11

What is the difference between request DTO and response DTO?

Answer:

A request DTO describes what the client sends to the server. A response DTO describes what the server returns to the client.


Question 12

Why should REST APIs usually return DTOs instead of entities?

Answer:

DTOs avoid exposing internal database structure, prevent sensitive field leaks, reduce lazy loading and infinite recursion issues, control response shape, and separate the API contract from the persistence model.


Question 13

How do I validate a request body?

Answer:

Add validation annotations to the request DTO, add spring-boot-starter-validation, and use @Valid with @RequestBody.


Question 14

What is the difference between @NotNull, @NotEmpty, and @NotBlank?

Answer:

@NotNull means the value cannot be null. @NotEmpty means it cannot be null or empty. @NotBlank means a string cannot be null, empty, or only whitespace.


Question 15

What happens if validation fails?

Answer:

Spring usually returns 400 Bad Request.


Question 16

What does ResponseEntity control?

Answer:

ResponseEntity controls HTTP status code, headers, and body.


Question 17

How do I return 201 Created?

Answer:

Use:

return ResponseEntity.status(HttpStatus.CREATED).body(body);

or:

return ResponseEntity.created(location).body(body);

Question 18

How do I return 204 No Content?

Answer:

Use:

return ResponseEntity.noContent().build();

Question 19

When should I use @ResponseStatus?

Answer:

Use @ResponseStatus when the response status is fixed and simple.


Question 20

What is the difference between ResponseEntity and @ResponseStatus?

Answer:

ResponseEntity gives flexible control over status, headers, and body. @ResponseStatus sets a fixed status for a controller method or exception.

Final Memory Sentences

  • Request body is data from client to server.
  • Response body is data from server to client.
  • @RequestBody reads body into a Java object.
  • @ResponseBody writes return value into the response body.
  • @RestController equals @Controller plus @ResponseBody.
  • HttpMessageConverter converts between HTTP bodies and Java objects.
  • Jackson usually converts JSON to Java and Java to JSON.
  • Serialization means Java to JSON.
  • Deserialization means JSON to Java.
  • DTOs define the API contract.
  • Request DTO and response DTO can be different.
  • REST APIs should usually return DTOs, not entities.
  • Add spring-boot-starter-validation for Bean Validation.
  • Validation annotations need @Valid to run on request bodies.
  • @NotBlank is best for required text fields.
  • ResponseEntity controls status, headers, and body.
  • 201 Created is good for create operations.
  • 204 No Content is good for successful delete without body.
  • @ResponseStatus is good for simple fixed status.