Spring Boot — Best Practices & AI-Driven Scaffolding
AI Context & Token Optimization
- MapStruct for Mapping: Never write manual DTO-to-Entity mapping code. AI agents often hallucinate field names during manual mapping. MapStruct ensures compile-time safety.
- Constructor Injection: Use Lombok's
@RequiredArgsConstructor. Field @Autowired obscures dependencies from the AI's static analysis.
- Hexagonal Boundaries: Keep the
domain/ layer completely free of Spring/JPA annotations to ensure pure, testable Java logic.
High-Performance Project Onboarding
Initialize any Spring Boot backend from scratch with these architectural rules:
- Domain-Driven Design (DDD): Use a pure
domain package containing entities, value objects, and repository ports (interfaces). The domain must not have adapter or framework dependencies.
- Hexagonal Ports & Adapters: Inbound adapters (Controllers, DTOs) and outbound adapters (JPA Repositories, Database engines) are decoupled. Controllers depend on domain services, and domain services interact with adapters via ports.
- Constructor Injection: Always use Lombok
@RequiredArgsConstructor on classes needing dependencies. Banned: Field @Autowired.
- MapStruct Compile-Time Mapping: Generate mappers using MapStruct
@Mapper(componentModel = "spring"). Banned: reflection-based mapping or manually writing setter chains.
- Centralized Error Boundary: Implement a single
@RestControllerAdvice class capturing all domain-specific exceptions and mapping them to standardized HTTP responses { error, message, status, timestamp }.
- Database Migration: Always use Flyway or Liquibase to manage relational schemas via SQL files in
resources/db/migration. Banned: relying on JPA hibernate.ddl-auto=update in production.
Project Structure
src/main/java/com/company/project/
├── adapter/ # Inbound & outbound adapters
│ ├── inbound/
│ │ ├── controller/ # REST controllers
│ │ └── dto/ # Request/response DTOs
│ └── outbound/
│ ├── repository/ # JPA / data repositories
│ └── mapper/ # MapStruct mappers
├── domain/
│ ├── entity/ # Domain entities (@Entity)
│ ├── service/ # Business logic
│ ├── exception/ # Domain exceptions
│ └── port/ # Repository interfaces (ports)
└── common/
├── config/ # @Configuration classes
├── exception/ # Global exception handler
└── util/ # Utility classes
src/main/resources/
├── application.yml # Default config
├── application-dev.yml # Dev profile
└── application-prod.yml # Production profile
Naming Conventions
| Artifact |
Convention |
Example |
| Packages |
lowercase.reverse.domain |
com.company.project.domain.service |
| Classes |
PascalCase |
UserServiceImpl |
| Methods |
camelCase |
findByEmail |
| REST endpoints |
plural nouns, kebab-case |
/api/users/{id} |
| Tables |
snake_case plural |
user_roles |
| Columns |
snake_case |
created_at |
Architectural Patterns
Domain-Driven Design (DDD)
Structure the application so that the domain is the innermost, most stable layer.
- Domain — Entities, value objects, domain services, repository ports. No framework annotations here (except
@Entity where unavoidable).
- Adapter — Controllers, DTOs, JPA repositories, mappers. These depend on the domain, not the other way around.
- Ports — Interfaces in the domain layer that adapters implement. For example, a
UserRepository interface in domain/port/ is implemented by JpaUserRepository in adapter/outbound/repository/.
MapStruct for Entity ↔ DTO Mapping
- Generate mapper implementations at compile time — no runtime reflection overhead.
- Keep mapping logic in dedicated
@Mapper interfaces; never hand-write set calls.
- Use
@Mapping annotations for field name differences.
Constructor Injection Over Field Injection
Always inject dependencies via the constructor (Lombok @RequiredArgsConstructor or explicit constructor). Never use @Autowired on fields — it makes testing and immutability harder.
Global Exception Handling
Create a single @RestControllerAdvice class that catches all exceptions.
- Map domain exceptions (e.g.,
UserNotFoundException) to specific HTTP status codes.
- Return a consistent error response body (
{ error, message, status, timestamp }).
- Log the stack trace at
ERROR level for 5xx; WARN for 4xx.
Universal DateTime Governance
- DTOs:
LocalDateTime and Date are BANNED in API DTOs. Use java.time.Instant for absolute timestamps and OffsetDateTime for human-readable boundaries. Use @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX") on OffsetDateTime fields.
- Domain/Entities: Store
java.time.Instant in entity fields mapped to TIMESTAMP WITH TIME ZONE columns.
- Clock Injection: Inject
java.time.Clock via constructor (@RequiredArgsConstructor). Never call Instant.now() or LocalDateTime.now() directly in business logic — always use clock.instant().
- API Serialization: Configure
spring.jackson to serialize Instant as epoch milliseconds and OffsetDateTime as ISO-8601 string with offset.
Testing Strategies
| Layer |
Test Type |
Framework |
File Naming |
| Domain service |
Unit |
JUnit 5 + Mockito |
UserServiceTest.java |
| Controller |
Slice test |
@WebMvcTest |
UserControllerTest.java |
| Repository |
Slice test |
@DataJpaTest |
UserRepositoryTest.java |
| Full integration |
Integration |
@SpringBootTest |
UserIntegrationTest.java |
- Use
@WebMvcTest and @DataJpaTest for focused tests — they bootstrap only the relevant context.
- Prefer
Mockito for mocking; never use PowerMock.
- Use
Testcontainers for database-dependent integration tests.
1---2name: spring-boot3description: DDD, hexagonal style, and naming conventions for Spring Boot4---56# Spring Boot — Best Practices & AI-Driven Scaffolding78## AI Context & Token Optimization9101. **MapStruct for Mapping:** Never write manual DTO-to-Entity mapping code. AI agents often hallucinate field names during manual mapping. MapStruct ensures compile-time safety.112. **Constructor Injection:** Use Lombok's `@RequiredArgsConstructor`. Field `@Autowired` obscures dependencies from the AI's static analysis.123. **Hexagonal Boundaries:** Keep the `domain/` layer completely free of Spring/JPA annotations to ensure pure, testable Java logic.1314## High-Performance Project Onboarding1516Initialize any Spring Boot backend from scratch with these architectural rules:17181. **Domain-Driven Design (DDD):** Use a pure `domain` package containing entities, value objects, and repository ports (interfaces). The domain must not have adapter or framework dependencies.192. **Hexagonal Ports & Adapters:** Inbound adapters (Controllers, DTOs) and outbound adapters (JPA Repositories, Database engines) are decoupled. Controllers depend on domain services, and domain services interact with adapters via ports.203. **Constructor Injection:** Always use Lombok `@RequiredArgsConstructor` on classes needing dependencies. Banned: Field `@Autowired`.214. **MapStruct Compile-Time Mapping:** Generate mappers using MapStruct `@Mapper(componentModel = "spring")`. Banned: reflection-based mapping or manually writing setter chains.225. **Centralized Error Boundary:** Implement a single `@RestControllerAdvice` class capturing all domain-specific exceptions and mapping them to standardized HTTP responses `{ error, message, status, timestamp }`.236. **Database Migration:** Always use Flyway or Liquibase to manage relational schemas via SQL files in `resources/db/migration`. Banned: relying on JPA `hibernate.ddl-auto=update` in production.2425## Project Structure2627```28src/main/java/com/company/project/29├── adapter/ # Inbound & outbound adapters30│ ├── inbound/31│ │ ├── controller/ # REST controllers32│ │ └── dto/ # Request/response DTOs33│ └── outbound/34│ ├── repository/ # JPA / data repositories35│ └── mapper/ # MapStruct mappers36├── domain/37│ ├── entity/ # Domain entities (@Entity)38│ ├── service/ # Business logic39│ ├── exception/ # Domain exceptions40│ └── port/ # Repository interfaces (ports)41└── common/42 ├── config/ # @Configuration classes43 ├── exception/ # Global exception handler44 └── util/ # Utility classes4546src/main/resources/47├── application.yml # Default config48├── application-dev.yml # Dev profile49└── application-prod.yml # Production profile50```5152## Naming Conventions5354| Artifact | Convention | Example |55| -------------- | -------------------------- | ------------------------------------ |56| Packages | `lowercase.reverse.domain` | `com.company.project.domain.service` |57| Classes | `PascalCase` | `UserServiceImpl` |58| Methods | `camelCase` | `findByEmail` |59| REST endpoints | plural nouns, `kebab-case` | `/api/users/{id}` |60| Tables | `snake_case` plural | `user_roles` |61| Columns | `snake_case` | `created_at` |6263## Architectural Patterns6465### Domain-Driven Design (DDD)6667Structure the application so that the domain is the innermost, most stable layer.6869- **Domain** — Entities, value objects, domain services, repository ports. No framework annotations here (except `@Entity` where unavoidable).70- **Adapter** — Controllers, DTOs, JPA repositories, mappers. These depend on the domain, not the other way around.71- **Ports** — Interfaces in the domain layer that adapters implement. For example, a `UserRepository` interface in `domain/port/` is implemented by `JpaUserRepository` in `adapter/outbound/repository/`.7273### MapStruct for Entity ↔ DTO Mapping7475- Generate mapper implementations at compile time — no runtime reflection overhead.76- Keep mapping logic in dedicated `@Mapper` interfaces; never hand-write `set` calls.77- Use `@Mapping` annotations for field name differences.7879### Constructor Injection Over Field Injection8081Always inject dependencies via the constructor (Lombok `@RequiredArgsConstructor` or explicit constructor). Never use `@Autowired` on fields — it makes testing and immutability harder.8283### Global Exception Handling8485Create a single `@RestControllerAdvice` class that catches all exceptions.8687- Map domain exceptions (e.g., `UserNotFoundException`) to specific HTTP status codes.88- Return a consistent error response body (`{ error, message, status, timestamp }`).89- Log the stack trace at `ERROR` level for 5xx; `WARN` for 4xx.9091## Universal DateTime Governance9293- **DTOs:** `LocalDateTime` and `Date` are BANNED in API DTOs. Use `java.time.Instant` for absolute timestamps and `OffsetDateTime` for human-readable boundaries. Use `@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")` on `OffsetDateTime` fields.94- **Domain/Entities:** Store `java.time.Instant` in entity fields mapped to `TIMESTAMP WITH TIME ZONE` columns.95- **Clock Injection:** Inject `java.time.Clock` via constructor (`@RequiredArgsConstructor`). Never call `Instant.now()` or `LocalDateTime.now()` directly in business logic — always use `clock.instant()`.96- **API Serialization:** Configure `spring.jackson` to serialize `Instant` as epoch milliseconds and `OffsetDateTime` as ISO-8601 string with offset.9798## Testing Strategies99100| Layer | Test Type | Framework | File Naming |101| ---------------- | ----------- | ----------------- | -------------------------- |102| Domain service | Unit | JUnit 5 + Mockito | `UserServiceTest.java` |103| Controller | Slice test | `@WebMvcTest` | `UserControllerTest.java` |104| Repository | Slice test | `@DataJpaTest` | `UserRepositoryTest.java` |105| Full integration | Integration | `@SpringBootTest` | `UserIntegrationTest.java` |106107- Use `@WebMvcTest` and `@DataJpaTest` for focused tests — they bootstrap only the relevant context.108- Prefer `Mockito` for mocking; never use `PowerMock`.109- Use `Testcontainers` for database-dependent integration tests.