This document provides guidelines for developing a Spring Boot project. It includes coding standards, Spring Boot best practices and testing recommendations to follow.
- Java 21 or later
- Docker and Docker Compose
- Maven (or use the included Maven wrapper)
Follow package-by-feature/module and in each module package-by-layer code organization style:
project-root/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/mycompany/projectname/
│ │ │ ├── config/
│ │ │ ├── module1/
│ │ │ │ ├── api/
│ │ │ │ │ ├── controllers/
│ │ │ │ │ └── dtos/
│ │ │ │ ├── config/
│ │ │ │ ├── domain/
│ │ │ │ │ ├── entities/
│ │ │ │ │ ├── exceptions/
│ │ │ │ │ ├── mappers/
│ │ │ │ │ ├── models/
│ │ │ │ │ ├── repositories/
│ │ │ │ │ └── services/
│ │ │ │ ├── jobs/
│ │ │ │ ├── eventhandlers/
│ │ └── resources/
│ │ └── application.properties
│ └── test/
│ │ └── java/
│ │ │ └── com/mycompany/projectname/
│ │ │ ├── module1/
│ │ │ │ ├── api/
│ │ │ │ │ ├── controllers/
│ │ │ │ ├── domain/
│ │ │ │ │ └── services/
└── README.md
-
Web Layer (
com.companyname.projectname.module.api
):- Controllers handle HTTP requests and responses
- DTOs for request/response data
- Global exception handling
-
Service Layer (
com.companyname.projectname.module.domain.services
):- Business logic implementation
- Transaction management
-
Repository Layer (
com.companyname.projectname.module.domain.repositories
):- Spring Data JPA repositories
- Database access
-
Entity Layer (
com.companyname.projectname.module.domain.entities
):- JPA entities representing database tables
-
Model Layer (
com.companyname.projectname.module.domain.models
):- DTOs for domain objects
- Command objects for operations
-
Mapper Layer (
com.companyname.projectname.module.domain.mappers
):- Converters from DTOs to JPA entities and vice-versa
-
Exceptions (
com.companyname.projectname.module.domain.exceptions
):- Custom exceptions
-
Config (
com.companyname.projectname.module.config
):- Spring Boot configuration classes such as WebMvcConfig, WebSecurityConfig, etc.
-
Java Code Style:
- Use Java 21 features where appropriate (records, text blocks, pattern matching, etc.)
- Follow standard Java naming conventions
- Use meaningful variable and method names
- Use
public
access modifier only when necessary
-
Testing Style:
- Use descriptive test method names
- Follow the Given-When-Then pattern
- Use AssertJ for assertions
-
Dependency Injection Style
- Don't use Spring Field Injection in production code.
- Use Constructor Injection without adding
@Autowired
.
-
Transactional Boundaries
- Annotate
@Service
classes with@Transactional(readOnly=true)
to manage transactions declaratively. - Annotate methods in
@Service
classes that perform write operations with@Transactional
. - Keep transactions as short as possible.
- Annotate
-
Don't use JPA entities in the "web" layer
- Instead, create dedicated Request/Response objects as Java records.
- Use Jakarta Validation annotations on Request object.
-
Create custom Spring Data JPA methods with meaningful method names using JPQL queries instead of using long derived query method names.
-
Create usecase specific Command objects and pass them to the "service" layer methods to perform create or update operations.
-
Application Configuration:
- Create all the application-specific configuration properties with a common prefix in
application.properties
file. - Use Typed Configuration with
@ConfigurationProperties
with validations.
- Create all the application-specific configuration properties with a common prefix in
-
Implement Global Exception Handling:
@ControllerAdvice
/@RestControllerAdvice
with@ExceptionHandler
methods.- Return consistent error payloads (e.g. a standard
ErrorResponse
DTO).
-
Logging:
- Never use
System.out.println()
for production logging. - Use SLF4J logging.
- Never use
-
Use WebJars for service static content.
-
Don't use Lombok.
Use Flyway for database migrations:
- Migration scripts should be in
src/main/resources/db/migration
- Naming convention:
V{version}__{description}.sql
- Hibernate is configured with
ddl-auto=validate
to ensure schema matches entities
- Unit Tests: Test individual components in isolation using mocks if required
- Integration Tests: Test interactions between components using Testcontainers
- Use descriptive test names that explain what the test is verifying
- Follow the Given-When-Then pattern for a clear test structure
- Use AssertJ for assertions for more readable assertions
- Prefer testing with real dependencies in unit tests as much as possible instead of using mocks
- Use Testcontainers for integration tests to test with real databases, message brokers, etc
- TestcontainersConfiguration.java: Configures database, message broker, etc containers for tests
- BaseIT.java: Base class for integration tests that sets up:
- Spring Boot test context using a random port
- MockMvcTester for HTTP requests
- Import
TestcontainersConfiguration.java
- Min 80% Code Coverage: Aim for good code coverage, but be pragmatic. Don't write useless tests just for the sake of code coverage metrics.