Tránh 9 lỗi thường gặp khi sử dụng Spring Boot REST API
Spring Boot là một trong những framework Java phổ biến nhất để xây dựng REST API. Tuy nhiên, trong quá trình phát triển, hầu như lập trình viên nào cũng sẽ gặp phải một số lỗi hoặc vấn đề quen thuộc. Trong bài viết này, chúng ta sẽ cùng điểm qua những lỗi phổ biến, tìm hiểu nguyên nhân và cách xử lý để giúp quá trình phát triển ứng dụng trở nên ổn định và hiệu quả hơn.

Giới thiệu
Việc xây dựng REST API với Spring Boot khá đơn giản và nhanh chóng. Tuy nhiên, trong quá trình phát triển, rất nhiều lập trình viên vẫn vô tình mắc phải những lỗi phổ biến, dẫn đến các vấn đề về hiệu năng, bảo mật cũng như khả năng bảo trì của hệ thống.
Trong bài viết này, chúng ta sẽ cùng điểm qua một số sai lầm thường gặp khi phát triển REST API với Spring Boot, đồng thời tìm hiểu cách khắc phục để xây dựng những API sạch, dễ mở rộng và tuân thủ các best practices.
1. Sử dụng sai HTTP Method
Sai lầm
Sử dụng HTTP Method không đúng cho các thao tác CRUD. Ví dụ, dùng POST để cập nhật dữ liệu thay vì PUT.
Một REST API được xây dựng bằng Spring Boot nên tuân thủ đúng các quy ước của RESTful để đảm bảo tính nhất quán và dễ bảo trì.
Ví dụ chưa đúng
@PostMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return ResponseEntity.ok(userService.updateUser(id, user));
}Giải pháp
Hãy tuân thủ đúng quy ước của RESTful:
GET: Lấy dữ liệuPOST: Tạo mới tài nguyênPUT: Cập nhật toàn bộ tài nguyênDELETE: Xóa tài nguyênPATCH: Cập nhật một phần tài nguyên
Ví dụ đúng
@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return ResponseEntity.ok(userService.updateUser(id, user));
}2. Không xử lý Exception đúng cách
Sai lầm
Trả về lỗi mặc định hoặc stack trace khi có exception xảy ra khiến việc debug trở nên khó khăn, đồng thời còn tiềm ẩn các lỗ hổng bảo mật.
Ví dụ chưa đúng
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id); // Throws exception if user not found
}Vấn đề: Khi không tìm thấy user, exception sẽ không được xử lý và API sẽ trả về lỗi
500 Internal Server Errorcùng với stack trace.
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
try {
return userService.getUser(id);
} catch (Exception e) {
return null; // Bad practice
}
}Vấn đề: Bắt toàn bộ exception khiến nguyên nhân thực sự của lỗi bị che giấu và rất khó xử lý.
Giải pháp
Sử dụng @ControllerAdvice để xử lý exception tập trung và trả về đúng HTTP Status Code.
Ví dụ đúng
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleNotFoundException(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}3. Làm lộ thông tin nội bộ của hệ thống
Sai lầm
Trả về trực tiếp Entity từ database, vô tình làm lộ cấu trúc dữ liệu hoặc những thông tin nhạy cảm.
Ví dụ chưa đúng
public class User {
private String name;
private String email;
private String password;
}
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}Vấn đề: API đang trả về cả trường
passwordvà các field nội bộ vốn không cần thiết đối với phía client.
Giải pháp
Sử dụng DTO (Data Transfer Object) để kiểm soát dữ liệu trả về.
Ví dụ đúng
public class User {
private String name;
private String email;
private String password;
}
public class UserDTO {
private String name;
private String email;
}
@GetMapping("/users/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserDTO(id));
}4. Bỏ qua Pagination và Filtering
Sai lầm
Trả về toàn bộ dữ liệu mà không hỗ trợ phân trang hoặc lọc dữ liệu.
Điều này có thể gây ảnh hưởng nghiêm trọng đến hiệu năng khi lượng dữ liệu ngày càng lớn, đồng thời làm giảm trải nghiệm của người dùng.
Ví dụ chưa đúng
@GetMapping("/users")
public List<User> getAllUsers() {
return userService.getAllUsers();
}Vấn đề: API có thể tiêu tốn rất nhiều bộ nhớ và thời gian xử lý khi bảng dữ liệu lớn.
Giải pháp
Sử dụng Pagination và Filtering. Với Spring Data JPA, bạn có thể dùng Pageable để hỗ trợ phân trang thông qua các tham số như page và size.
Ví dụ đúng
@GetMapping("/users")
public ResponseEntity<Page<User>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy
) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return userRepository.findAll(pageable);
}5. Không validate dữ liệu đầu vào
Sai lầm
Cho phép xử lý những request chứa dữ liệu không hợp lệ hoặc thiếu thông tin.
Điều này có thể dẫn đến dữ liệu sai lệch trong database hoặc tạo ra các lỗ hổng bảo mật.
Ví dụ chưa đúng
public class User {
private String name;
private String email;
private String password;
}
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.ok(userService.createUser(user));
}Giải pháp
Sử dụng Bean Validation với các annotation như @Valid, @NotNull, @Email...
Ví dụ đúng
public class User {
private String name;
private String email;
private String password;
}
public class UserRequest {
@NotNull
private String name;
@Email
private String email;
}
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserRequest request) {
return ResponseEntity.ok(userService.createUser(request));
}6. Trả về sai HTTP Status Code
Sai lầm
API trả về HTTP Status Code không đúng với ý nghĩa của thao tác thực hiện.
Ví dụ chưa đúng
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.createUser(user); // Returns 200 OK
}
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return new User();
}
return user;
}Giải pháp
Sử dụng @ResponseStatus hoặc ResponseEntity để trả về đúng HTTP Status Code.
Ví dụ đúng
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}Hoặc
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}7. Đặt tên REST API không nhất quán
Sai lầm
Mỗi endpoint được đặt theo một kiểu khác nhau, khiến API thiếu tính nhất quán và khó sử dụng.
Ví dụ chưa đúng
@RestController
public class UserController {
@GetMapping("/getUsers")
public List<User> getUsers() { ... }
@PostMapping("/createNewUser")
public User createNewUser(@RequestBody User user) { ... }
@PutMapping("/updateUserDetails/{userId}")
public User updateUserDetails(@PathVariable Long userId) { ... }
}Giải pháp
Sử dụng @RequestMapping để định nghĩa prefix chung cho toàn bộ resource.
Ví dụ đúng
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping
public List<User> getUsers() { ... }
@PostMapping
public User createUser(@RequestBody User user) { ... }
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id) { ... }
}8. Bỏ qua các nguyên tắc bảo mật
Sai lầm
Không triển khai cơ chế xác thực (Authentication) và phân quyền (Authorization).
Ví dụ chưa đúng
@GetMapping("/admin")
public String adminAccess() {
return "Admin Panel";
}Vấn đề: Bất kỳ ai cũng có thể truy cập endpoint dành cho quản trị viên.
Giải pháp
Sử dụng Spring Security kết hợp JWT để xác thực và bảo vệ các endpoint.
Ví dụ đúng
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}9. Hardcode thông tin cấu hình
Sai lầm
Đặt trực tiếp thông tin cấu hình như tài khoản database, API Key hoặc Secret vào source code.
Ví dụ chưa đúng
String dbUrl = "jdbc:mysql://localhost:3306/mydb";
String dbUser = "root";
String dbPassword = "password";Vấn đề: Hardcode thông tin nhạy cảm không chỉ gây rủi ro về bảo mật mà còn khiến việc thay đổi cấu hình ở các môi trường khác nhau trở nên khó khăn.
Giải pháp
Lưu cấu hình trong application.properties, application.yaml hoặc tốt hơn là sử dụng Environment Variables.
Ví dụ đúng
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DATABASE_USER}
spring.datasource.password=${DATABASE_PASSWORD}Kết luận
Việc tránh những sai lầm phổ biến khi phát triển REST API sẽ giúp hệ thống có hiệu năng tốt hơn, an toàn hơn và dễ bảo trì hơn trong dài hạn.
Bằng cách tuân thủ các best practices như sử dụng đúng HTTP Method, xử lý exception tập trung, validate dữ liệu đầu vào, sử dụng DTO, áp dụng Pagination và bảo mật endpoint bằng Spring Security, bạn sẽ xây dựng được những REST API ổn định, dễ mở rộng và sẵn sàng cho môi trường production.