Hey developers! π
So you're building REST APIs with Spring Boot? Cool! But are you doing it right? I've seen way too many APIs that work but... let's just say they could be better. A lot better.
Today I'm sharing 10 best practices that'll make your Spring Boot APIs cleaner, more maintainable, and actually enjoyable to work with. Let's dive in!
1. Use Consistent and RESTful Resource Naming
β Don't do this:
@RestController
@RequestMapping("/user") // Singular? Nope!
public class UserController {
// ...
}
β
Do this instead:
@RestController
@RequestMapping("/users") // Always plural!
public class UserController {
@GetMapping // Just this, no "/getAllUsers"
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@PostMapping // Not "/createUser"
public ResponseEntity<?> createUser(@RequestBody UserRequest request) {
// ...
}
}
Why? Keep it simple and follow REST conventions. Your API consumers will thank you.
2. Return the Correct HTTP Status Codes
Stop returning 200 OK
for everything! Here's what you should actually return:
@PostMapping
public ResponseEntity<?> createUser(@RequestBody UserRequest request) {
User user = userService.createUser(request);
return ResponseEntity
.status(HttpStatus.CREATED) // 201, not 200!
.build();
}
Quick reference:
-
200
- OK (for successful GET, PUT) -
201
- Created (for successful POST) -
204
- No Content (for successful DELETE) -
400
- Bad Request (validation errors) -
404
- Not Found -
500
- Internal Server Error
3. Never Expose Your Entities (Use DTOs!)
β This is dangerous:
@PostMapping
public User createUser(@RequestBody User user) { // DON'T!
return userService.save(user);
}
Why? Because you might accidentally expose sensitive data like passwords!
β
Use DTOs instead:
// Request DTO
public record UserRequest(
String name,
String email,
String password
) {}
// Response DTO
public record UserResponse(
Integer id,
String name,
String email
// No password here!
) {}
@PostMapping
public UserResponse createUser(@RequestBody UserRequest request) {
return userService.createUser(request);
}
4. Use Bean Validation (Stop Writing If Statements!)
β Please don't do this:
@PostMapping
public ResponseEntity<?> createUser(@RequestBody UserRequest request) {
if (request.getName().isBlank()) {
throw new IllegalArgumentException("Name is required");
}
if (request.getEmail().isBlank()) {
throw new IllegalArgumentException("Email is required");
}
// More if statements... π΅
}
β
Use validation annotations:
public record UserRequest(
@NotBlank(message = "Name is required")
String name,
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
String email,
@Size(min = 8, message = "Password must be at least 8 characters")
String password
) {}
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody UserRequest request) {
// Validation happens automatically!
return userService.createUser(request);
}
5. Separate Your Concerns (Controller β Service β Repository)
Don't put business logic in your controllers!
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService; // Inject service
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody UserRequest request) {
UserResponse response = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
}
@Service
public class UserService {
private final UserRepository userRepository; // Inject repository
public UserResponse createUser(UserRequest request) {
// Business logic goes here
User user = new User(request.name(), request.email());
User savedUser = userRepository.save(user);
return new UserResponse(savedUser.getId(), savedUser.getName(), savedUser.getEmail());
}
}
6. Always Use Pagination
Never return all records at once. Seriously, never.
@GetMapping
public Page<UserResponse> getAllUsers(Pageable pageable) {
return userService.getAllUsers(pageable);
}
Your API calls will look like:
GET /users?page=0&size=10&sort=name,asc
7. Centralize Exception Handling
β Don't handle exceptions everywhere:
@PostMapping
public ResponseEntity<?> createUser(@RequestBody UserRequest request) {
try {
// ...
} catch (Exception e) {
return ResponseEntity.badRequest().body("Something went wrong");
}
}
β
Use @ControllerAdvice:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationErrors(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<?> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.notFound().build();
}
}
8. Implement Security (Don't Skip This!)
I'm not going to implement it here, but please secure your APIs! Options include:
- JWT tokens
- OAuth2
- Basic authentication
- API keys
Use Spring Security β it's your friend.
9. Version Your APIs
Always version your APIs from day one:
@RestController
@RequestMapping("/api/v1/users") // Version it!
public class UserController {
// ...
}
When you need to make breaking changes, create /api/v2/users
and keep v1 running until everyone migrates.
10. Document Your APIs
Use tools like:
- Swagger/OpenAPI (most popular)
- Spring REST Docs
- Postman Collections
Your future self (and your teammates) will thank you.
Bonus Tip: Test Your APIs!
Write integration tests for your endpoints:
@SpringBootTest
@AutoConfigureTestDatabase
class UserControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldCreateUser() {
UserRequest request = new UserRequest("John", "john@example.com", "password123");
ResponseEntity<UserResponse> response = restTemplate.postForEntity(
"/api/v1/users", request, UserResponse.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody().name()).isEqualTo("John");
}
}
Wrapping Up
These practices might seem like "extra work" at first, but trust me β they'll save you hours of debugging and refactoring later. Your APIs will be:
- β More maintainable
- β Easier to test
- β More secure
- β Better documented
- β Actually enjoyable to work with
What's your biggest Spring Boot API pain point? Drop a comment below β I'd love to help!
Top comments (0)