Top 10 Spring Boot REST API Best Practices (With Code Examples)
backend
4 min read
Explore the top 10 Spring Boot REST API best practices for creating clean, secure, and scalable APIs. Enhance your API development skills with practical code examples and expert tips for professional-grade Spring Boot applications.
Published By: Nelson Djalo | Date: May 5, 2025
Spring Boot makes it easy to build REST APIs quickly, but writing clean, secure, and scalable APIs still requires following best practices. In this guide, you'll learn the top 10 Spring Boot REST API best practices that will help you write professional-grade APIs ready for production.
Whether you're building internal tools or public APIs, these tips will help you avoid common mistakes, improve maintainability, and impress your team.
Always use plural nouns and avoid action words in your URLs.
❌ Bad
@GetMapping("/getAllUsers")
✅ Good
@GetMapping("api/v1/users")
Keep endpoints clean and meaningful:
api/v1/users
– get all usersapi/v1/users/{id}
– get user by IDapi/v1/users
(POST) – create new userapi/v1/users/{id}
(PUT/PATCH) – update userapi/v1/users/{id}
(DELETE) – delete userAlways return status codes that reflect what happened on the server. For example use 201 Created when creating resources instead of returning 200 OK.
@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@RequestBody @Valid UserRequest request) {
UserDto created = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
Common status codes:
200 OK
– Success201 Created
– New resource created204 No Content
– Successfully deleted400 Bad Request
– Validation failed404 Not Found
– Resource not found500 Internal Server Error
– Something went wrongNever expose your database entities directly. Use Data Transfer Objects (DTOs).
❌ Bad:
public ResponseEntity<User> getUser(@PathVariable Long id) { ... }
✅ Good:
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
User user = userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("User not found"));
return ResponseEntity.ok(new UserResponse(user.getId(), user.getName()));
}
Map between DTOs and entities in your service layer to protect internal data and structure.
Avoid manual if-checks and use Jakarta Bean Validation (@Valid, @NotBlank, etc.).
❌ Bad:
if (user.getName().isBlank()) {
throw new IllegalArgumentException("Name is required");
}
✅ Good:
public record UserRequest(
@NotBlank String name,
@NotBlank String password
) {}
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody @Valid UserRequest request) { ... }
Structure your code using the Controller-Service-Repository pattern.
Controller → Service → Repository → Database
Example:
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity<UserDto> create(@RequestBody @Valid UserRequest req) {
return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(req));
}
}
@Service
public class UserService {
private final UserRepository userRepository;
public UserDto createUser(UserRequest req) {
User user = new User(req.name(), req.password());
userRepository.save(user);
return new UserDto(user.getId(), user.getName());
}
}
Never return thousands of records at once. Use pagination with Spring Data.
Controller Example:
@GetMapping
public Page<UserDto> getUsers(Pageable pageable) {
return userService.getUsers(pageable);
}
Sample request:
GET /users?page=0&size=10&sort=name,asc
Handle errors in one place using @ControllerAdvice.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidation(MethodArgumentNotValidException ex) {
return ResponseEntity.badRequest().body("Validation failed: " + ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAll(Exception ex) {
return ResponseEntity.status(500).body("Internal error: " + ex.getMessage());
}
}
Secure your endpoints using Spring Security:
Example:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
}
Keep old versions alive by versioning your endpoints.
Example:
@RequestMapping("/api/v1/users")
public class UserControllerV1 { ... }
@RequestMapping("/api/v2/users")
public class UserControllerV2 { ... }
Avoid breaking changes to existing consumers.
Use SpringDoc OpenAPI or Swagger UI for interactive documentation.
Add dependency:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
Access documentation at:
http://localhost:8080/swagger-ui.html
It helps frontend developers and third parties understand your API.
By following these best practices, you'll write cleaner, more maintainable, and secure REST APIs with Spring Boot.
Get unlimited access to coding courses, Quizzes, Builds and Tools. Start your journey or level up your career with Amigoscode today!