Hello! Welcome back to our course on backend development with Java and Spring Boot.
In our last few lessons, we've focused on handling incoming data. We learned how to deserialize a JSON request body with @RequestBody and how to extract data from the URL using @PathVariable and @RequestParam. Now, it's time to shift our attention to the other side of the conversation: the response.
A well-designed REST API communicates clearly and predictably. A big part of that is sending back the right HTTP status code and headers. While Spring Boot provides sensible defaults (like 200 OK for a successful GET request), real-world applications require more precise control.
This lesson is dedicated to mastering the HTTP response. You will learn how to return appropriate HTTP status codes and headers using ResponseEntity. This will give you fine-grained control over what your API sends back to the client, allowing you to signal success, creation of new resources, errors, and more, all according to REST conventions.
1. Full Control with ResponseEntity
In your previous controller methods, you might have returned a Java object directly (like a List<Product> or a User object). When you do this, Spring Boot implicitly wraps it in an HTTP response, setting the status to 200 OK and using a JSON converter to create the body.
But what if you want to change the status code? Or add a custom header? For this, Spring provides the ResponseEntity class.
ResponseEntity is a generic class that represents the entire HTTP response: the status code, the headers, and the body. By having your controller method return a ResponseEntity, you take full control.
Let's start by reading a brief introduction to ResponseEntity and its basic usage.
Using Spring ResponseEntity to Manipulate the HTTP ...
This article from Baeldung provides a concise introduction to ResponseEntity and shows a basic example of its use.
Read the sections '1. Introduction' and '2. ResponseEntity'. Focus on the definition: it represents the whole HTTP response. Notice the first code example where a new ResponseEntity is created.
As you saw, you can create a ResponseEntity using its constructor. However, a more common and readable approach is to use its fluent builder pattern. This pattern uses a chain of method calls to construct the response, which is generally preferred.
The next resource shows how to use this builder pattern and also how to set custom headers.
Using Spring ResponseEntity to Manipulate the HTTP ...
This part of the same Baeldung article explains the powerful builder interface for ResponseEntity. It provides a much cleaner way to build your responses.
Read from the paragraph 'Additionally, we can set HTTP headers:' to the end of the article. Pay close attention to the examples that use static methods like ResponseEntity.ok(), ResponseEntity.badRequest(), and the chaining of .header() and .body() methods.
To summarize the two main approaches you've just seen:
- Constructor:
new ResponseEntity<>(body, headers, status); - Builder Pattern:
ResponseEntity.status(HttpStatus.OK).header("Custom-Header", "foo").body("Your body here");
The builder pattern also provides convenient shortcuts for common status codes, such as ResponseEntity.ok() and ResponseEntity.badRequest(). This is the style you'll see and use most often.
2. Practical Examples for Core HTTP Methods
Now that you understand what ResponseEntity is, let's see how to apply it in the context of a real REST controller. We'll look at the conventional responses for POST, PUT, and DELETE operations.
Creating a Resource: POST and 201 Created
When a client sends a POST request to create a new resource, a 200 OK status is acceptable, but it's not the most descriptive. The HTTP specification provides a more specific status: 201 Created. This explicitly tells the client that a new resource was successfully created.
Furthermore, a 201 Created response should include a Location header. This header contains the URL where the newly created resource can be accessed.
Let's see how to implement this with ResponseEntity.
Getting Started | Building REST services with Spring
The official Spring 'Building REST services' guide provides an excellent, practical example of returning a 201 Created status with a Location header after creating a resource.
Read the subsection titled 'Proper Responses'. Focus on the newEmployee method. Notice how it now returns ResponseEntity<?> and uses ResponseEntity.created(...).body(...) to build the response.
The key piece of code from that example is:
@PostMapping("/employees")
ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) {
// ... code to save the newEmployee and get its URI ...
// For example, let's assume the new employee has ID 3
URI location = URI.create("/employees/3");
return ResponseEntity
.created(location) // Sets status to 201 and adds Location header
.body(savedEmployee); // The body contains the created employee
}
This pattern is a cornerstone of well-behaved REST APIs.
Deleting a Resource: DELETE and 204 No Content
When a client successfully deletes a resource with a DELETE request, there's usually no data to return in the response body. Sending a 200 OK with an empty body works, but a more precise status code is 204 No Content. This tells the client that the request was successful and that there is intentionally no response body.
The implementation is very clean with ResponseEntity.
Getting Started | Building REST services with Spring
The Spring guide also shows the standard way to handle a DELETE request.
Find the deleteEmployee method in the 'Handling DELETE requests' subsection. Observe the simplicity of returning a 204 No Content response.
The code is as straightforward as it gets:
@DeleteMapping("/employees/{id}")
ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
repository.deleteById(id);
return ResponseEntity.noContent().build();
}
The noContent() method sets the status to 204, and build() finalizes the response entity without a body.
Handling Client Errors: 4xx Status Codes
ResponseEntity is also your tool for communicating client-side errors. For example, if a client requests a resource that doesn't exist (GET /employees/999), you should return a 404 Not Found. If they provide invalid data, a 400 Bad Request is appropriate.
Let's look at a simple example of returning a different status code based on some logic. The "age" calculation example from the Baeldung article we saw earlier is perfect for this.
@GetMapping("/age")
ResponseEntity<String> age(@RequestParam("yearOfBirth") int yearOfBirth) {
if (isInFuture(yearOfBirth)) {
// Return a 400 Bad Request status with an error message in the body
return ResponseEntity.badRequest()
.body("Year of birth cannot be in the future");
}
// Return a 200 OK status with the calculated age
return ResponseEntity.ok("Your age is " + calculateAge(yearOfBirth));
}
This conditional logic is fundamental to building robust APIs. You check the input or state, and then return the appropriate ResponseEntity for that scenario.
Test your understanding!
You are implementing a PUT /products/{id} endpoint to update an existing product. You need to handle two scenarios:
- The product with the given
idis found, and you successfully update it. - The product with the given
iddoes not exist in the database.
Based on what you've learned, what ResponseEntity would you return in each case? Write the two return statements.
Show answer
Here is how you would handle the two scenarios:
-
Product Found and Updated: Return a
200 OKwith the updated product in the body.return ResponseEntity.ok(updatedProduct); -
Product Not Found: Return a
404 Not Found. You can use thenotFound()builder method.return ResponseEntity.notFound().build();
Conclusion
You now have a powerful tool in your backend development toolkit. By using ResponseEntity, you've moved beyond Spring Boot's default behavior and can now craft precise, standard-compliant HTTP responses that make your API robust, predictable, and easier for client applications to consume.
Key Takeaways:
ResponseEntitygives you complete control over the HTTP response, including the status code, headers, and body.- The builder pattern (
ResponseEntity.ok(),.status(),.header(),.body()) provides a clean and readable way to construct responses. - Use specific status codes to accurately communicate the result of an operation:
201 Createdfor a successfulPOST, along with aLocationheader.204 No Contentfor a successfulDELETEwhere no body is returned.404 Not Foundwhen a requested resource doesn't exist.400 Bad Requestwhen the client provides invalid data.
What's Next?
In our examples, we manually checked input (yearOfBirth) to return a 400 Bad Request. While this works, it can become repetitive and clutter your controller logic. Spring Boot offers a more elegant and declarative way to handle input validation.
In our next lesson, we will explore how to validate request data using Bean Validation annotations (@NotNull, @Size, @Valid). You'll learn how to automatically validate incoming request bodies and have Spring trigger a 400 Bad Request response for you, keeping your controller code clean and focused on its core responsibilities.