Spring Boot Project Patterns
Intermediate
Follow standard Spring Boot patterns — constructor injection over field injection, proper layer separation, configuration externalization, and annotation best practices.
File Patterns
**/*.java**/application*.yml**/pom.xml**/build.gradle*
This rule applies to files matching the patterns above.
Rule Content
rule-content.md
# Spring Boot Project Patterns
## Rule
Spring Boot projects MUST use constructor injection (never field injection), follow the controller-service-repository layering, and externalize all configuration to application.yml.
## Good Examples
### Constructor Injection (Required)
```java
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
// Constructor injection — immutable, testable
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
}
```
### Layer Separation
```
com.myapp/
├── controller/ # REST endpoints — no business logic
├── service/ # Business logic — no HTTP concerns
├── repository/ # Data access — no business logic
├── model/ # Domain entities
├── dto/ # Data transfer objects
├── config/ # Spring configuration classes
└── exception/ # Custom exceptions + handler
```
### Configuration
```yaml
# application.yml — externalized config
app:
cache:
ttl: 3600
max-size: 1000
retry:
max-attempts: 3
delay-ms: 1000
```
```java
@ConfigurationProperties(prefix = "app.cache")
public record CacheConfig(int ttl, int maxSize) {}
```
## Bad Examples
```java
// BAD: Field injection — not testable, hidden dependencies
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // Field injection!
@Autowired
private PasswordEncoder encoder; // Field injection!
}
// BAD: Business logic in controller
@RestController
public class UserController {
@PostMapping("/users")
public User create(@RequestBody CreateUserRequest request) {
// Validation, hashing, saving — all in controller!
String hashed = encoder.encode(request.password());
User user = new User(request.name(), hashed);
return repository.save(user); // Direct repo access from controller
}
}
// BAD: Hardcoded configuration
private static final int TIMEOUT = 5000; // Should be in application.yml
```
## Enforcement
- ArchUnit tests to enforce layer dependencies
- Checkstyle rule to flag @Autowired on fields
- Code review checklist for Spring patternsFAQ
Discussion
Loading comments...