Skip to content

Java Coding Guidelines

Language Version

Java 25 (LTS). Adopt new language features actively.

Modern Java Features

  • Records for immutable data carriers — prefer over plain POJOs.
  • Sealed classes for closed type hierarchies.
  • Pattern matching (instanceof, switch expressions) — prefer over casting.
  • Text blocks for multiline strings.
  • Optional<T> as return type instead of returning null; never pass Optional as a parameter.
  • Stream API for collection operations; prefer over manual loops.
  • Immutable collections: List.of(), Map.of(), Set.of().

Naming

  • camelCase — methods, variables, parameters.
  • PascalCase — classes, interfaces, enums, records.
  • SCREAMING_SNAKE_CASE — constants.
  • Interface names: noun or adjective (Repository, Serializable), not IRepository.

Type Aliases and Domain Modelling

  • Use single-field records as type aliases for domain concepts: record Amount(BigDecimal value) {}.
  • Prefer BigDecimal over double for monetary values.

Dependency Injection

  • Constructor injection only — never field injection (@Autowired on fields is forbidden).
  • Prefer final fields for all injected dependencies.

Error Handling

  • Prefer unchecked exceptions for programming errors; checked exceptions for recoverable conditions callers must handle.
  • Never declare throws Exception — declare specific exception types.
  • Never swallow exceptions with empty catch blocks.
  • Store an exception's data in constructor-set fields with accessors (e.g. bookingId()), not only inside the inherited message — callers use the value directly (logging, API error details) instead of parsing getMessage().
  • One exception class per distinct error condition is idiomatic here — cheap to declare, and the type itself is what a catch clause dispatches on. Unlike C++ (see cpp_coding.md), don't consolidate into a single generic exception with an error-code field just to reduce class count.

Build

  • Gradle (Kotlin DSL) preferred for new projects; Maven acceptable.
  • Enforce code style with Checkstyle in CI; config at checkstyle.xml (copy from templates/).

Frameworks

  • Services: Spring Boot — preferred.
  • Configuration: application.yml; override with environment variables via spring-dotenv or OS env.

Testing

  • JUnit 6 for unit tests (JUnit 5 still supported for migration).
  • Mockito for mocking at architectural boundaries only.
  • AssertJ for fluent assertions.
  • Cucumber JVM + JUnit Platform for BDD scenarios.
  • Structure test bodies with // GIVEN, // WHEN, // THEN.

General Advice

  • Avoid too much hidden magic (e.g. Java Annotation Processing). Better make things more explicit and visible. Never use Mapstruct.
  • When using Spring (Boot)
  • Use constructor injection
  • @autowire prohibited

Directives

  • Java 25; use Records for immutable data, sealed classes for closed hierarchies, pattern matching over casting
  • Optional<T> as return type instead of null; never pass Optional as a parameter
  • Constructor injection only — @Autowired on fields is forbidden; all injected dependencies must be final
  • Never declare throws Exception; declare specific types; never swallow exceptions with empty catch
  • Exceptions expose their data via accessors, not only via the inherited message; one exception class per distinct error condition is idiomatic here (cheap, dispatchable by catch) — don't consolidate for C++-style reasons
  • Use single-field Records as type aliases: record Amount(BigDecimal value) {}; BigDecimal for monetary values
  • Spring Boot for services; application.yml for config, override with environment variables
  • JUnit 6 + AssertJ + Mockito; Cucumber JVM for BDD; structure test bodies with // GIVEN, // WHEN, // THEN
  • Never use Mapstruct; avoid hidden annotation processing magic; keep things explicit