Stack conventions
Java (Maven / Gradle)
Java 17+ services and libraries built with Maven or Gradle (Spring Boot or plain Java), tested with JUnit 5.
Java 17+ services and libraries built with Maven or Gradle (Spring Boot or plain Java), tested with JUnit 5.
Used for: java-maven, java-gradle.
Principles
- Use the language level the build declares; on Java 17/21+ prefer records for data carriers, sealed types for closed hierarchies and pattern matching (
instanceof,switch) over casts. - Favour immutability:
finalfields, records,List.of/Map.of/copyOffor unmodifiable collections, defensive copies of mutable inputs. - Use
Optionalonly as a return type for 'may be absent'; never for fields, parameters or collections, and never callget()without a guarantee — useorElse*,map,ifPresent. - Close every resource with try-with-resources; never rely on finalisation.
- Inject dependencies through constructors (single constructor,
finalfields); no field injection. - Put transaction boundaries on service methods (
@Transactional,readOnly = truefor reads), never on controllers, and keep remote calls out of open transactions. - Throw specific exceptions, preserve the cause when wrapping, and never swallow exceptions; use unchecked exceptions for programming errors and domain failures the caller cannot recover from.
- Log through SLF4J with parameterised messages (
log.info("Order {} shipped", id)), never string concatenation orSystem.out. - Program to interfaces where there are or will be several implementations; do not add an interface for every class by reflex.
- Override
equalsandhashCodetogether (records do it for you), and never use mutable fields in them.
Conventions
Accessibility
Concurrency
- Prefer
java.util.concurrent(executors,CompletableFuture, concurrent collections) over raw threads andsynchronizedblocks. - On Java 21+, consider virtual threads for blocking I/O workloads where the framework supports them; avoid
synchronizedaround blocking I/O with virtual threads on JDKs before 24. - Share immutable objects between threads; document thread-safety of mutable classes.
Dependencies
- Manage versions through the Spring Boot BOM, a Maven
dependencyManagementBOM or a Gradle version catalog (libs.versions.toml). - Use the build wrapper (
./mvnw,./gradlew) committed to the repo; do not add dependencies that duplicate the JDK or existing libraries.
Documentation
- Javadoc on public API types and methods: what it does, parameters, return, exceptions; no Javadoc that restates the signature.
- Comment why, not what; keep the README's build, run and configuration steps current when they change.
Errors
- Never catch
Exception/Throwablebroadly except at a top-level boundary that logs and translates. - Wrap with context:
throw new OrderException("cannot ship " + id, e)— keep the cause. - Map exceptions to HTTP responses in one place (
@RestControllerAdvicewithProblemDetail).
Logging
- One
private static final Logger log = LoggerFactory.getLogger(X.class)per class (or the repo's Lombok@Slf4j). - Log an exception once, where it is handled, passing the exception as the last argument; never log secrets or personal data.
Naming
- UpperCamelCase classes, lowerCamelCase methods and fields, UPPER_SNAKE_CASE constants, lowercase package names without underscores.
- Test classes named
<ClassUnderTest>Test; integration tests*ITwhen the build separates them.
Performance
- Avoid N+1 queries: fetch joins, entity graphs or projections; set
spring.jpa.open-in-view=falsein new services. - Use
StringBuilderin loops, primitive streams for numeric work, and size collections when the size is known. - Measure with a profiler or JMH before optimising.
Security
- Use parameterised queries (JPA parameters,
JdbcTemplatewith?/named parameters); never concatenate input into JPQL/SQL. - Validate request bodies with Bean Validation (
@Valid, constraints) at the controller boundary. - Never deserialise untrusted data with Java serialisation; configure Jackson without default typing.
- Keep secrets in environment variables or a secret store, not in
application.ymlcommitted to the repo.
Structure
- Standard Maven/Gradle layout:
src/main/java,src/test/java, resources alongside. - Package by feature (
com.acme.orders) rather than by layer only, unless the repo is already layered. - Keep classes small and cohesive; minimise visibility (package-private by default,
publiconly for the API).
Testing
- JUnit 5 (Jupiter) with AssertJ or the repo's assertion library; Mockito for collaborators you own.
- Use Spring test slices (
@WebMvcTest,@DataJpaTest) for focused tests and@SpringBootTestsparingly. - Use Testcontainers for database and messaging integration tests instead of in-memory substitutes with different behaviour.
- Tests are deterministic: inject
Clock, avoidThread.sleep, and use Awaitility for asynchronous expectations. - Unit tests (
*Test) run with Surefire or the Gradle test task; integration tests (*IT) run with Failsafe or a separate source set and use Testcontainers. - Name test methods after the behaviour (
rejectsOrderWithoutLines, or@DisplayName), one behaviour per test. - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Are dependencies injected through constructors into final fields?
- Is Optional used only as a return type and never unwrapped with an unchecked get()?
- Are data carriers records or otherwise immutable?
- Are all resources closed with try-with-resources?
- Are transaction boundaries on public service methods with readOnly for reads?
- Are exceptions specific, with causes preserved, and none swallowed?
- Are log statements parameterised and free of secrets?
- Are queries parameterised and free of N+1 access patterns?
- Is request input validated at the boundary?
- Are equals/hashCode consistent and based on immutable state?
- Do tests use JUnit 5, cover the behaviour, and avoid sleeps?
- Are new dependencies version-managed through the BOM or version catalog?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
checkstyle-maven |
lint | checkstyle | no |
checkstyle-gradle |
lint | checkstyle | no |
spotless-maven |
format | spotless | no |
spotless-gradle |
format | spotless | no |
spotbugs-maven |
static | spotbugs | no |
pmd-maven |
static | pmd | no |
dependency-check-maven |
security | OWASP dependency-check | no |
dependency-check-gradle |
security | OWASP dependency-check | no |
maven-verify |
test | mvn verify | no |
gradle-check |
test | gradle check | no |
osv-scanner |
security | osv-scanner | no |
gitleaks |
security | gitleaks | no |