Skip to main content

ResponseEntity in Spring Boot

Table of Contents

  1. What is ResponseEntity?
  2. Theory and Concepts
  3. Basic Usage
  4. ResponseEntity Methods
  5. Working with DTOs
  6. HTTP Status Codes
  7. Headers Management
  8. Best Practices
  9. Real-world Examples

What is ResponseEntity?

ResponseEntity is a Spring Framework class that represents the entire HTTP response including the status code, headers, and body. It provides fine-grained control over the HTTP response in Spring Boot REST APIs.

Key Features:

  • Complete HTTP Response Control: Status code, headers, and body
  • Type Safety: Generic type support for response body
  • Flexible: Can be used with or without response body
  • Builder Pattern: Fluent API for easy construction

Theory and Concepts

HTTP Response Structure

HTTP/1.1 200 OK                    ← Status Line
Content-Type: application/json ← Headers
Content-Length: 85
Cache-Control: no-cache

{ ← Body
"id": 1,
"name": "John Doe"
}

ResponseEntity Anatomy

ResponseEntity<T> = Status Code + Headers + Body
  • Status Code: HTTP status (200, 404, 500, etc.)
  • Headers: HTTP headers (Content-Type, Authorization, etc.)
  • Body: Response payload (JSON, XML, plain text, etc.)

Basic Usage

Simple ResponseEntity

@RestController
public class UserController {

// Basic usage - just status
@GetMapping("/health")
public ResponseEntity<String> health() {
return ResponseEntity.ok("Service is running");
}

// With custom status
@PostMapping("/users")
public ResponseEntity<String> createUser() {
// Business logic here
return ResponseEntity.status(HttpStatus.CREATED)
.body("User created successfully");
}

// No content response
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
// Delete logic here
return ResponseEntity.noContent().build();
}
}

Constructor vs Builder Pattern

// Constructor approach
return new ResponseEntity<>("Hello World", HttpStatus.OK);

// Builder pattern (Recommended)
return ResponseEntity.ok()
.header("Custom-Header", "value")
.body("Hello World");

ResponseEntity Methods

Static Factory Methods

Success Responses

// 200 OK
ResponseEntity.ok()
ResponseEntity.ok("body")
ResponseEntity.ok().body(object)

// 201 Created
ResponseEntity.status(HttpStatus.CREATED)
ResponseEntity.created(uri)

// 204 No Content
ResponseEntity.noContent()

Error Responses

// 400 Bad Request
ResponseEntity.badRequest()
ResponseEntity.badRequest().body("Invalid input")

// 404 Not Found
ResponseEntity.notFound()

// 500 Internal Server Error
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

Generic Status

ResponseEntity.status(HttpStatus.ACCEPTED)
ResponseEntity.status(202) // Status code as integer

Builder Methods

ResponseEntity.ok()
.header("X-Custom-Header", "value")
.contentType(MediaType.APPLICATION_JSON)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.body(responseBody);

Working with DTOs

DTO Classes

// User DTO
public class UserDTO {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;

// Constructors
public UserDTO() {}

public UserDTO(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = LocalDateTime.now();
}

// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }

public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

// Request DTO
public class CreateUserRequestDTO {
@NotBlank(message = "Name is required")
private String name;

@Email(message = "Invalid email format")
@NotBlank(message = "Email is required")
private String email;

// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}

// Response Wrapper DTO
public class ApiResponseDTO<T> {
private boolean success;
private String message;
private T data;
private LocalDateTime timestamp;

public ApiResponseDTO(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
this.timestamp = LocalDateTime.now();
}

// Static factory methods
public static <T> ApiResponseDTO<T> success(T data) {
return new ApiResponseDTO<>(true, "Success", data);
}

public static <T> ApiResponseDTO<T> success(String message, T data) {
return new ApiResponseDTO<>(true, message, data);
}

public static <T> ApiResponseDTO<T> error(String message) {
return new ApiResponseDTO<>(false, message, null);
}

// Getters and Setters
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }

public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }

public T getData() { return data; }
public void setData(T data) { this.data = data; }

public LocalDateTime getTimestamp() { return timestamp; }
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
}

Controller with DTOs

@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {

@Autowired
private UserService userService;

// GET - Single User
@GetMapping("/{id}")
public ResponseEntity<ApiResponseDTO<UserDTO>> getUserById(@PathVariable Long id) {
try {
UserDTO user = userService.findById(id);
if (user != null) {
return ResponseEntity.ok(ApiResponseDTO.success(user));
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponseDTO.error("User not found"));
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Internal server error"));
}
}

// GET - All Users with Pagination
@GetMapping
public ResponseEntity<ApiResponseDTO<List<UserDTO>>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
try {
List<UserDTO> users = userService.findAll(page, size);
return ResponseEntity.ok(ApiResponseDTO.success("Users retrieved successfully", users));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to retrieve users"));
}
}

// POST - Create User
@PostMapping
public ResponseEntity<ApiResponseDTO<UserDTO>> createUser(
@Valid @RequestBody CreateUserRequestDTO request) {
try {
UserDTO createdUser = userService.createUser(request);

URI location = URI.create("/api/users/" + createdUser.getId());

return ResponseEntity.created(location)
.header("X-Created-Resource", "User")
.body(ApiResponseDTO.success("User created successfully", createdUser));

} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Invalid input: " + e.getMessage()));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to create user"));
}
}

// PUT - Update User
@PutMapping("/{id}")
public ResponseEntity<ApiResponseDTO<UserDTO>> updateUser(
@PathVariable Long id,
@Valid @RequestBody CreateUserRequestDTO request) {
try {
UserDTO updatedUser = userService.updateUser(id, request);
if (updatedUser != null) {
return ResponseEntity.ok(ApiResponseDTO.success("User updated successfully", updatedUser));
} else {
return ResponseEntity.notFound().build();
}
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Invalid input: " + e.getMessage()));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to update user"));
}
}

// DELETE - Delete User
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponseDTO<Void>> deleteUser(@PathVariable Long id) {
try {
boolean deleted = userService.deleteUser(id);
if (deleted) {
return ResponseEntity.ok(ApiResponseDTO.success("User deleted successfully", null));
} else {
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to delete user"));
}
}
}

HTTP Status Codes

Common Status Codes in Spring Boot

2xx Success

// 200 OK - Request successful
ResponseEntity.ok(data)

// 201 Created - Resource created
ResponseEntity.status(HttpStatus.CREATED).body(data)

// 204 No Content - Success but no content to return
ResponseEntity.noContent().build()

// 202 Accepted - Request accepted for processing
ResponseEntity.accepted().build()

4xx Client Errors

// 400 Bad Request - Invalid request
ResponseEntity.badRequest().body("Invalid data")

// 401 Unauthorized - Authentication required
ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication required")

// 403 Forbidden - Access denied
ResponseEntity.status(HttpStatus.FORBIDDEN).body("Access denied")

// 404 Not Found - Resource not found
ResponseEntity.notFound().build()

// 409 Conflict - Resource conflict
ResponseEntity.status(HttpStatus.CONFLICT).body("Resource already exists")

// 422 Unprocessable Entity - Validation errors
ResponseEntity.unprocessableEntity().body(validationErrors)

5xx Server Errors

// 500 Internal Server Error
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Server error")

// 503 Service Unavailable
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Service temporarily unavailable")

Headers Management

Common Headers

@GetMapping("/users/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
UserDTO user = userService.findById(id);

return ResponseEntity.ok()
.header("X-Total-Count", "1")
.header("X-Request-ID", UUID.randomUUID().toString())
.contentType(MediaType.APPLICATION_JSON)
.cacheControl(CacheControl.maxAge(300, TimeUnit.SECONDS))
.lastModified(user.getLastModified())
.eTag(user.getVersion().toString())
.body(user);
}

// Multiple headers
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getUsers() {
List<UserDTO> users = userService.findAll();

HttpHeaders headers = new HttpHeaders();
headers.add("X-Total-Count", String.valueOf(users.size()));
headers.add("X-Page-Number", "1");
headers.add("X-Page-Size", "10");
headers.add("Access-Control-Allow-Origin", "*");

return ResponseEntity.ok()
.headers(headers)
.body(users);
}

CORS Headers

@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getUsers() {
List<UserDTO> users = userService.findAll();

return ResponseEntity.ok()
.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
.header("Access-Control-Allow-Headers", "Content-Type, Authorization")
.body(users);
}

Best Practices

1. Consistent Response Structure

// Always use a consistent response wrapper
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private Map<String, Object> metadata;

// Methods...
}

2. Proper Error Handling

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ApiResponseDTO<Void>> handleEntityNotFound(EntityNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponseDTO.error(ex.getMessage()));
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponseDTO<Map<String, String>>> handleValidation(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));

return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Validation failed").setData(errors));
}
}

3. Use Appropriate HTTP Methods and Status Codes

@RestController
@RequestMapping("/api/users")
public class UserController {

@GetMapping("/{id}") // 200 OK or 404 NOT_FOUND
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) { /* ... */ }

@PostMapping // 201 CREATED
public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO user) { /* ... */ }

@PutMapping("/{id}") // 200 OK or 404 NOT_FOUND
public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody UserDTO user) { /* ... */ }

@DeleteMapping("/{id}") // 204 NO_CONTENT or 404 NOT_FOUND
public ResponseEntity<Void> deleteUser(@PathVariable Long id) { /* ... */ }
}

4. Location Header for Created Resources

@PostMapping
public ResponseEntity<UserDTO> createUser(@RequestBody CreateUserRequestDTO request) {
UserDTO createdUser = userService.createUser(request);

URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdUser.getId())
.toUri();

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

Real-world Examples

E-commerce Product API

@RestController
@RequestMapping("/api/products")
public class ProductController {

@Autowired
private ProductService productService;

@GetMapping
public ResponseEntity<PageResponseDTO<ProductDTO>> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String category,
@RequestParam(required = false) String sortBy) {

PageResponseDTO<ProductDTO> products = productService.getProducts(page, size, category, sortBy);

return ResponseEntity.ok()
.header("X-Total-Elements", String.valueOf(products.getTotalElements()))
.header("X-Total-Pages", String.valueOf(products.getTotalPages()))
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.body(products);
}

@PostMapping
public ResponseEntity<ApiResponseDTO<ProductDTO>> createProduct(
@Valid @RequestBody CreateProductRequestDTO request,
HttpServletRequest httpRequest) {

try {
ProductDTO product = productService.createProduct(request);

String location = httpRequest.getRequestURL().toString() + "/" + product.getId();

return ResponseEntity.status(HttpStatus.CREATED)
.header("Location", location)
.header("X-Resource-ID", product.getId().toString())
.body(ApiResponseDTO.success("Product created successfully", product));

} catch (ProductAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponseDTO.error("Product with this SKU already exists"));
}
}

@PatchMapping("/{id}/stock")
public ResponseEntity<ApiResponseDTO<ProductDTO>> updateStock(
@PathVariable Long id,
@RequestBody UpdateStockRequestDTO request) {

try {
ProductDTO updatedProduct = productService.updateStock(id, request.getQuantity());

return ResponseEntity.ok()
.header("X-Stock-Updated", "true")
.body(ApiResponseDTO.success("Stock updated successfully", updatedProduct));

} catch (InsufficientStockException e) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Insufficient stock available"));
} catch (ProductNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
}

File Upload/Download API

@RestController
@RequestMapping("/api/files")
public class FileController {

@PostMapping("/upload")
public ResponseEntity<ApiResponseDTO<FileDTO>> uploadFile(
@RequestParam("file") MultipartFile file) {

try {
if (file.isEmpty()) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("File cannot be empty"));
}

FileDTO uploadedFile = fileService.uploadFile(file);

return ResponseEntity.status(HttpStatus.CREATED)
.header("X-File-Size", String.valueOf(file.getSize()))
.header("X-File-Type", file.getContentType())
.body(ApiResponseDTO.success("File uploaded successfully", uploadedFile));

} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to upload file"));
}
}

@GetMapping("/download/{id}")
public ResponseEntity<Resource> downloadFile(@PathVariable Long id) {
try {
FileResource fileResource = fileService.getFile(id);

return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(fileResource.getContentType()))
.contentLength(fileResource.getSize())
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileResource.getFilename() + "\"")
.body(fileResource.getResource());

} catch (FileNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
}

Authentication API

@RestController
@RequestMapping("/api/auth")
public class AuthController {

@PostMapping("/login")
public ResponseEntity<ApiResponseDTO<LoginResponseDTO>> login(
@Valid @RequestBody LoginRequestDTO request) {

try {
LoginResponseDTO response = authService.authenticate(request);

return ResponseEntity.ok()
.header("Authorization", "Bearer " + response.getAccessToken())
.header("X-Token-Expires-In", String.valueOf(response.getExpiresIn()))
.body(ApiResponseDTO.success("Login successful", response));

} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponseDTO.error("Invalid credentials"));
}
}

@PostMapping("/refresh")
public ResponseEntity<ApiResponseDTO<TokenResponseDTO>> refreshToken(
@RequestHeader("Authorization") String refreshToken) {

try {
TokenResponseDTO response = authService.refreshToken(refreshToken);

return ResponseEntity.ok()
.header("Authorization", "Bearer " + response.getAccessToken())
.body(ApiResponseDTO.success("Token refreshed successfully", response));

} catch (TokenExpiredException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponseDTO.error("Refresh token expired"));
}
}

@PostMapping("/logout")
public ResponseEntity<ApiResponseDTO<Void>> logout(
@RequestHeader("Authorization") String token) {

authService.logout(token);

return ResponseEntity.ok()
.header("X-Logout-Time", String.valueOf(System.currentTimeMillis()))
.body(ApiResponseDTO.success("Logout successful", null));
}
}

Conclusion

ResponseEntity is a powerful tool in Spring Boot that provides complete control over HTTP responses. When combined with DTOs, it enables the creation of robust, type-safe REST APIs with proper status codes, headers, and response structures.

Key Takeaways:

  • Always use appropriate HTTP status codes
  • Implement consistent response structures with DTOs
  • Handle errors gracefully with proper status codes
  • Use headers effectively for metadata and caching
  • Follow RESTful principles in your API design
  • Implement proper validation and error handling