name: java-spring description: Best practices for Java/Spring projects. Use when working with Spring Boot applications, Spring Framework code, or any Java project using Spring ecosystem. Enforces DDD principles, Testcontainers for integration testing, explicit code without Lombok, MapStruct for entity/DTO mapping with strict layer boundaries, Spring Data JPA with proper transaction management, and conservative dependency management. Triggers on Spring Boot, Spring Framework, Spring Data, Spring MVC, Spring WebFlux, or general Java backend development tasks.
Java/Spring Development Standards
Core Principles
1. Domain-Driven Design (DDD)
Structure code around business capabilities, not technical layers. See references/ddd-patterns.md for patterns and examples.
Avoid: technical-layer slicing
com.acme.app.controller
com.acme.app.service
com.acme.app.repository
Prefer: business modules (bounded contexts)
com.acme.app/
├── billing/
│ ├── domain/ # Invoice, Payment, Money, BillingPolicy
│ ├── application/ # IssueInvoiceUseCase, RecordPaymentUseCase
│ ├── infrastructure/# JpaInvoiceRepository, PaymentGatewayClient
│ └── api/ # BillingController, request/response DTOs
├── catalog/
│ ├── domain/
│ ├── application/
│ ├── infrastructure/
│ └── api/
└── identity/
└── ...
Key rules:
- Top-level packages are business modules (bounded contexts)
- Technical layers (
domain/application/infrastructure/api) are inside each module - A change like "adjust invoice rules" should touch one module (
billing/), not hop across layers - Cross-module calls go through explicit APIs (use cases, ports, domain events)
2. No Lombok
Write explicit Java code. Do not use Lombok annotations (@Data, @Getter, @Builder, etc.).
Why: Explicit code is debuggable, IDE-friendly, and avoids compile-time magic issues.
Instead of Lombok:
- Use Java records for immutable data carriers
- Generate getters/setters via IDE
- Write explicit builders when needed
- Use
Objects.equals()andObjects.hash()for equals/hashCode
3. Conservative Dependency Management
Before adding a new library:
- Check if functionality exists in current dependencies
- Check if Spring Boot starters already provide it
- Check if Java standard library covers the use case
Analyze classpath first:
./mvnw dependency:tree
# or
./gradlew dependencies
4. Spring Data JPA
Use Spring Data JPA with Hibernate for persistence. Key practices:
- Entities: Explicit getters,
@Versionfor optimistic locking, protected no-arg constructor - Fetching: Avoid N+1 with
JOIN FETCH,@EntityGraph, or DTO projections - Transactions:
@Transactionalon service methods,readOnly = truefor queries
See references/data-access.md for entity mapping and repository patterns. See references/transactions.md for transaction management rules.
5. Testcontainers for Integration Testing
Use Testcontainers for any test requiring external dependencies (databases, message brokers, etc.).
See references/testing.md for setup and patterns.
6. MapStruct for Entity/DTO Mapping
Use MapStruct for all structural conversions between layers. Enforce strict boundaries:
- Controllers: Accept/return DTOs only, never touch entities
- Services: Invoke mappers, resolve relationships by ID, call domain behavior
- Mappers: Pure structural conversion only, no repository access, no business logic
- Domain: Independent of DTOs and web layer
See references/mapping.md for patterns and examples.
Workflow
Before Writing Code
Analyze existing dependencies:
./mvnw dependency:tree | grep -E "(spring-data|lombok|mapstruct|test)"Verify Spring Data JPA: Ensure
spring-boot-starter-data-jpais present.Check for MapStruct: If not present and DTOs needed, add
mapstruct+mapstruct-processor.Check for Lombok: If
lombokin dependencies, discuss removal strategy with user before proceeding.
When Implementing Features
- Start with domain model — entities and value objects
- Define repository interfaces — in domain layer
- Create DTOs — request/response records per use case
- Create MapStruct mappers — pure structural conversion
- Implement application services — orchestrate domain operations, invoke mappers
- Add API layer last — controllers are thin adapters, DTOs only
When Writing Tests
- Unit tests — domain logic, no Spring context
- Integration tests — use
@SpringBootTest+ Testcontainers - Slice tests —
@DataJpaTestor@WebMvcTestfor focused testing
Quick Reference
| Scenario | Action |
|---|---|
| Need a DTO | Use Java record |
| Need entity | Write class with explicit getters, equals/hashCode |
| Need builder | Write static inner Builder class |
| Need JSON mapping | Use Jackson annotations on records |
| Need validation | Use Jakarta Bean Validation (@NotNull, etc.) |
| Need database | Use Spring Data JPA repository |
| Need caching | Check if spring-boot-starter-cache already present |
| Need HTTP client | Check for WebClient or RestClient before adding new lib |
| Need DTO→Entity | Use MapStruct mapper, resolve relationships in service |
| Need Entity→DTO | Use MapStruct mapper, ensure graph is fetched first |
| Need partial update | Use @MappingTarget with IGNORE null strategy |
| Controller needs entity | NO — return DTO from service, never expose entities |
| Service modifies data | Add @Transactional on public method |
| Read-only query | Add @Transactional(readOnly = true) |
| Concurrent modifications | Use @Version for optimistic locking |
| External call in transaction | NO — use transactional outbox pattern |
Reference Files
- DDD Patterns — Aggregates, entities, value objects, domain events
- Data Access — Spring Data JPA entities, repositories, fetching strategies
- Transactions — JPA transaction management, propagation, locking
- Testing — Testcontainers setup and integration test patterns
- Mapping — MapStruct patterns, layer boundaries, DTO design