Spring Boot API Response Design
Best Practice
- Use Standard HTTP Status Codes: Return appropriate HTTP status codes (e.g., 200 for success, 404 for not found, 400 for bad request, 500 for server error).
- Consistent Response Format: Ensure that all API responses follow a consistent structure.
- Include Relevant Information: Provide necessary information like status, data, error messages, and timestamps.
- Error Handling: Clearly distinguish between different types of errors (e.g., validation errors, authentication errors).
- Documentation: Document the response structure in your API documentation.
Sample Response Structure
{ "timestamp": "2024-06-23T18:25:43.511Z", "status": 200, "message": "Success", "data": { "id": 123, "name": "John Doe", "email": "john.doe@example.com" }, "errors": [] }
Create a Response Wrapper Class
import java.time.LocalDateTime;
import java.util.List;
public class ApiResponse<T> {
private LocalDateTime timestamp;
private int status;
private String message;
private T data;
private List<String> errors;
public ApiResponse() {
this.timestamp = LocalDateTime.now();
}
public ApiResponse(int status, String message, T data, List<String> errors) {
this();
this.status = status;
this.message = message;
this.data = data;
this.errors = errors;
}
// Getters and Setters
}
Controller Example
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user")
public ResponseEntity<ApiResponse<User>> getUser() {
User user = new User(123, "John Doe", "john.doe@example.com");
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.OK.value(),
"Success",
user,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
User Model Example
public class User {
private int id;
private String name;
private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters and Setters
}
Examples Using ApiResponse
Creating a New Resource (POST)
@PostMapping("/user")
public ResponseEntity<ApiResponse<User>> createUser(@RequestBody User user) {
// Assume userService.saveUser(user) saves the user and returns the saved user object
User savedUser = userService.saveUser(user);
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.CREATED.value(),
"User created successfully",
savedUser,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
Handling Validation Errors
@PostMapping("/user")
public ResponseEntity<ApiResponse<User>> createUser(@Valid @RequestBody User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<String> errors = bindingResult.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList());
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.BAD_REQUEST.value(),
"Validation failed",
null,
errors
);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
User savedUser = userService.saveUser(user);
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.CREATED.value(),
"User created successfully",
savedUser,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
Retrieving a Resource (GET)
@GetMapping("/user/{id}")
public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable int id) {
User user = userService.findUserById(id);
if (user == null) {
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.NOT_FOUND.value(),
"User not found",
null,
Collections.singletonList("User with ID " + id + " not found")
);
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.OK.value(),
"User retrieved successfully",
user,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.OK);
}
Updating a Resource (PUT)
@PutMapping("/user/{id}")
public ResponseEntity<ApiResponse<User>> updateUser(@PathVariable int id, @RequestBody User user) {
User updatedUser = userService.updateUser(id, user);
if (updatedUser == null) {
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.NOT_FOUND.value(),
"User not found",
null,
Collections.singletonList("User with ID " + id + " not found")
);
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
ApiResponse<User> response = new ApiResponse<>(
HttpStatus.OK.value(),
"User updated successfully",
updatedUser,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.OK);
}
Deleting a Resource (DELETE)
@DeleteMapping("/user/{id}")
public ResponseEntity<ApiResponse<Void>> deleteUser(@PathVariable int id) {
boolean isDeleted = userService.deleteUser(id);
if (!isDeleted) {
ApiResponse<Void> response = new ApiResponse<>(
HttpStatus.NOT_FOUND.value(),
"User not found",
null,
Collections.singletonList("User with ID " + id + " not found")
);
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
ApiResponse<Void> response = new ApiResponse<>(
HttpStatus.NO_CONTENT.value(),
"User deleted successfully",
null,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.NO_CONTENT);
}
Returning a List of Users
@GetMapping("/users")
public ResponseEntity<ApiResponse<List<User>>> getAllUsers() {
List<User> users = userService.findAllUsers();
ApiResponse<List<User>> response = new ApiResponse<>(
HttpStatus.OK.value(),
"Users retrieved successfully",
users,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.OK);
}
Error Handling in ApiResponse
Global Exception Handler Class
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import java.util.Collections;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleAllExceptions(Exception ex, WebRequest request) {
ApiResponse<Void> response = new ApiResponse<>(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"An internal server error occurred",
null,
Collections.singletonList(ex.getMessage())
);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
// You can add more methods to handle specific exceptions here
}
Modify Your Controllers to Use the Global Exception Handler
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users")
public ResponseEntity<ApiResponse<List<User>>> getAllUsers() {
List<User> users = userService.findAllUsers();
ApiResponse<List<User>> response = new ApiResponse<>(
HttpStatus.OK.value(),
"Users retrieved successfully",
users,
Collections.emptyList()
);
return new ResponseEntity<>(response, HttpStatus.OK);
}
// Other controller methods...
}
Testing the Global Exception Handler
To test this, you can introduce a bug or throw an exception in your service or controller method. For example, modify the findAllUsers
method to throw an exception:
public List<User> findAllUsers() {
throw new RuntimeException("Simulated server error");
}
When you make a request to /api/users
, the global exception handler will catch the exception and return a standardized error response.
Extending the Global Exception Handler
You can add more methods to handle specific exceptions, such as ResourceNotFoundException
, ValidationException
, etc.
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ApiResponse<Void> response = new ApiResponse<>(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
null,
Collections.singletonList(ex.getMessage())
);
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}