| name | spring |
| description | [Applies to: **/*.java] Definitive guidelines for building robust, maintainable, and performant Spring Boot applications using modern best practices. |
| source | cursor_mdc |
Spring Best Practices
This guide outlines essential best practices for developing with Spring Boot, ensuring your applications are well-structured, performant, secure, and easily testable.
1. Code Organization and Structure
Maintain a clean, logical package structure.
1.1. Base Package and @SpringBootApplication
Place your main application class at the root of a well-named base package. All components should reside within this base package or its sub-packages.
❌ BAD:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
✅ GOOD:
package com.company.project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
1.2. Configuration Classes
Group related bean definitions into dedicated @Configuration classes.
❌ BAD:
@SpringBootApplication
public class MyApplication {
@Bean
public DataSource dataSource() { }
@Bean
public RestTemplate restTemplate() { }
}
✅ GOOD:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() { }
}
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder.baseUrl("http://api.example.com").build();
}
}
2. Common Patterns and Anti-patterns
2.1. Constructor Injection
Always prefer constructor injection for mandatory dependencies. It ensures immutability and testability.
❌ BAD:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
✅ GOOD:
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
2.2. Immutable Data Models
Use immutable classes for DTOs and entities where possible, especially for value objects.
❌ BAD:
public class UserDto {
private Long id;
private String name;
}
✅ GOOD:
public record UserDto(Long id, String name) {}
@Value
public class UserDto {
Long id;
String name;
}
3. Performance Considerations
3.1. N+1 Query Problem
Avoid N+1 queries by eagerly fetching related data when necessary.
❌ BAD:
@Service
public class OrderService {
@Autowired private OrderRepository orderRepository;
public List<Order> getOrdersWithItems() {
List<Order> orders = orderRepository.findAll();
orders.forEach(order -> order.getItems().size());
return orders;
}
}
✅ GOOD:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o JOIN FETCH o.items")
List<Order> findAllWithItems();
}
@Service
public class OrderService {
@Autowired private OrderRepository orderRepository;
public List<Order> getOrdersWithItems() {
return orderRepository.findAllWithItems();
}
}
4. Common Pitfalls and Gotchas
4.1. Disabling Auto-configuration
Only disable auto-configuration when strictly necessary to avoid unexpected behavior.
❌ BAD:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MyApplication { }
✅ GOOD:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication { }
5. Security Best Practices
5.1. Input Validation
Always validate user input at the API boundary. Use Spring's validation features.
❌ BAD:
@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto) {
userService.save(userDto);
return ResponseEntity.ok(userDto);
}
✅ GOOD:
@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@Valid @RequestBody UserDto userDto) {
userService.save(userDto);
return ResponseEntity.ok(userDto);
}
public record UserDto(@NotNull Long id, @NotBlank @Size(min = 2, max = 50) String name) {}
6. Error Handling
6.1. Global Exception Handling
Use @ControllerAdvice for consistent global error handling.
❌ BAD:
@RestController
public class UserController {
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
try {
return userService.findById(id);
} catch (UserNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found", e);
}
}
}
✅ GOOD:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleUserNotFound(UserNotFoundException ex) {
return new ErrorResponse("USER_NOT_FOUND", ex.getMessage());
}
}
@RestController
public class UserController {
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
public class UserNotFoundException extends RuntimeException { }
public record ErrorResponse(String code, String message) {}
7. API Design
7.1. RESTful Principles and DTOs
Design REST APIs following standard principles and use DTOs to decouple internal models from external API contracts.
❌ BAD:
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired private ProductService productService;
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
}
✅ GOOD:
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
@Autowired private ProductService productService;
@GetMapping("/{id}")
public ProductResponseDto getProduct(@PathVariable Long id) {
Product product = productService.findById(id);
return ProductMapper.toDto(product);
}
}
public record ProductResponseDto(Long id, String name, BigDecimal price) {}
8. Testing Approaches
8.1. Layered Testing
Use specific Spring Boot test annotations for different layers.
❌ BAD:
@SpringBootTest
public class UserServiceTest {
@Autowired private UserService userService;
}
✅ GOOD:
@ExtendWith(MockitoExtension.class)
public class UserServiceUnitTest {
@Mock private UserRepository userRepository;
@InjectMocks private UserService userService;
@Test
void findById_shouldReturnUser() {
}
}
@WebMvcTest(UserController.class)
public class UserControllerWebMvcTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Test
void getUser_shouldReturnOk() throws Exception {
}
}
@DataJpaTest
public class UserRepositoryIntegrationTest {
@Autowired private TestEntityManager entityManager;
@Autowired private UserRepository userRepository;
@Test
void findById_shouldReturnProduct() {
}
}
@SpringBootTest
{
MockMvc mockMvc;
}