Skip to content

Windsurf Rules for Spring Boot Services

Windsurf.windsurfrulesCoding

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.

@library0 upvotes0 remixes0 comments

Get this skill

Add the skill file with the CLI, by hand, or over MCP.

Save this to your project or home directory so Windsurf can load it.

npx goodprompts add windsurf-spring-boot-conventions
Saves to./.windsurfrules
npx goodprompts add windsurf-spring-boot-conventions
Saves to ./.windsurfrules

Skill file

.windsurfrules3,141 characters
# 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.
Comments

Comments

Sign in to commentSign in

Remixes

Loading remixes…