Windsurf Rules for Spring Boot Services
Spring Boot rules enforcing constructor injection, record-based DTOs mapped at the controller edge, and Testcontainers instead of mocked repositories.
Use when: Use in a Spring Boot 3.x / Java 17+ service when you want Windsurf to stop suggesting field injection, entity leakage into controllers, and H2-based integration tests.
The skill file
Windsurf · .windsurfrules.windsurfrules3141 chars
# Spring Boot Service Rules
## Dependency injection
- Constructor injection only. `@Autowired` on fields or setters is forbidden; with a single constructor the annotation is unnecessary — omit it.
- All injected fields are `private final`. If a class needs more than 5 dependencies, split it instead of adding a sixth.
- No `@Component` on classes that hold no state and have one public static-like method — make it a plain class or static utility.
- Never use `ApplicationContext.getBean()` or `@Lazy` to dodge a circular dependency; restructure the cycle.
## DTOs and mapping
- JPA entities never cross the controller boundary in either direction. Controllers accept and return `record` DTOs only.
- Request and response DTOs are separate records (`CreateOrderRequest`, `OrderResponse`) — never reuse one record for both.
- Mapping lives in a dedicated mapper (MapStruct or a small hand-written class in the same package as the DTO). No mapping logic inside controllers or services.
- Validate request DTOs with `jakarta.validation` annotations plus `@Valid` on the controller parameter; do not re-validate the same constraints in the service.
- DTO fields that can legitimately be absent use `Optional` accessors or explicit nullability comments — never silent `null` propagation through three layers.
## Persistence
- Repository interfaces declare intent-revealing methods (`findActiveByCustomerId`); avoid `@Query` with string concatenation, and never build JPQL from user input.
- `@Transactional` goes on service methods, never on controllers or repositories. Mark read paths `@Transactional(readOnly = true)`.
- Schema changes happen only through Flyway migrations under `db/migration`; `ddl-auto` stays at `validate` in every profile, including tests.
- Fetch strategy is `LAZY` for all associations; load what you need with `@EntityGraph` or fetch joins.
## Error handling
- One `@RestControllerAdvice` owns all exception-to-response mapping. Controllers and services never build `ResponseEntity` error bodies inline.
- Throw specific exceptions (`OrderNotFoundException extends NoSuchElementException`), never bare `RuntimeException` with a message.
- Error responses follow RFC 7807 problem-detail shape via `ProblemDetail`.
## Testing with Testcontainers
- Integration tests run against a real PostgreSQL Testcontainer via `@ServiceConnection`. H2 is forbidden everywhere.
- Define one shared abstract base class (or `@TestConfiguration`) that starts a single reusable container for the whole suite — never one container per test class.
- Repository and service tests use the container; only pure mapping/calculation logic gets Mockito-free plain unit tests.
- Web-layer tests use `@WebMvcTest` with mocked services; full-stack happy paths use `@SpringBootTest` + the container.
- Every Flyway migration must be exercised by the test suite booting against the container — a migration that breaks startup must fail CI.
## Workflow
- Run `./mvnw verify` (or `./gradlew check`) before suggesting a commit; format with Spotless if configured.
- Never edit files under `target/`, `build/`, or generated MapStruct sources.
Install
drop into your repoSave this to your project or home directory so Windsurf can load it.
path./.windsurfrules
Discussion
What people are saying
Forks
0 forks of this skillLoading forks…