# Kickoff Java Project — Shared Conventions Shared conventions for all scaffolded modules. Other files reference this — do not duplicate. Primary reference: **Janus** (Spring Boot 4.0.3, Java 25, jOOQ). Secondary: **Cerberus** (Spring Boot 3.5, Hibernate). --- ## JSpecify Nullability Spring Boot 4 deprecates `org.springframework.lang` nullability in favor of **JSpecify** (`org.jspecify.annotations`). All new scaffolds use JSpecify exclusively. - **Dependency**: `org.jspecify:jspecify:1.0.0` in parent `dependencyManagement` and as a dependency in modules that annotate code. - **`@NullMarked`**: Apply at the **top-level package** of each Maven module via `package-info.java`. Subpackages inherit automatically — do NOT add `package-info.java` to every subpackage. - **`@Nullable`**: Use `org.jspecify.annotations.Nullable` on nullable fields, parameters, and return types. Non-null by default — do NOT add redundant `== null` checks. - **Do NOT use**: `org.springframework.lang.NonNullApi`, `NonNullFields`, or `Nullable`. ```java @NullMarked package co.humand..; import org.jspecify.annotations.NullMarked; ``` --- ## Spring Boot 4 Starter Renames These starters were renamed or introduced in Spring Boot 4. Use the **New (SB4)** names in all scaffolded POMs. | Old (SB3) | New (SB4) | |-----------|-----------| | `spring-boot-starter-web` | `spring-boot-starter-webmvc` | | `spring-kafka` | `spring-boot-starter-kafka` | | `spring-kafka-test` | `spring-boot-starter-kafka-test` | | `spring-boot-starter-aop` | `spring-boot-starter-aspectj` | | `spring-boot-starter-oauth2-resource-server` | `spring-boot-starter-security-oauth2-resource-server` | | `spring-security-test` | `spring-boot-starter-security-test` | | `liquibase-core` (direct dep) | `spring-boot-starter-liquibase` | | `jackson-databind` (direct dep) | `spring-boot-starter-json` | | `spring-boot-starter-data-jpa` | `spring-boot-starter-jpa` | Starters that kept the same name: `spring-boot-starter-jooq`, `spring-boot-starter-cache`, `spring-boot-starter-data-redis`, `spring-boot-starter-validation`, `spring-boot-starter-actuator`, `spring-boot-starter-security`, `spring-boot-starter-logging`, `spring-boot-starter-test`. ### Redis Autoconfig Class Change | SB3 | SB4 | |-----|-----| | `org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration` | `org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration` | | `org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration` | `org.springframework.boot.data.redis.autoconfigure.DataRedisRepositoriesAutoConfiguration` | --- ## Configuration Class Visibility In Spring Boot 4, `@Configuration` classes must be **public** if they are imported from other packages (cross-module `@Import`). Package-private classes cause `IllegalAccessError` at runtime. Applies to: `KafkaConfiguration`, `SecurityConfiguration`, `CacheConfiguration`, `BaseTestConfiguration`, `TestContainersConfiguration`. --- ## Application Class Pattern ```java @SpringBootApplication(scanBasePackages = "co.humand.") @Import(SecurityConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` - `scanBasePackages` discovers beans across all modules under `co.humand.` (including kafka, core, model). - `@Import` is needed only for **security** — it lives in a separate module and is the most critical to ensure loads. Kafka and other configs are discovered by `scanBasePackages`. --- ## Test Framework (SB4) Spring Boot 4 changed test package locations and introduced new patterns: | Concept | SB3 | SB4 (Janus) | |---------|-----|-------------| | TestRestTemplate | `org.springframework.boot.test.web.client.TestRestTemplate` | `org.springframework.boot.resttestclient.TestRestTemplate` | | AutoConfigure annotation | `@AutoConfigureTestRestTemplate` (from `...test.web.client`) | `@AutoConfigureTestRestTemplate` (from `...resttestclient.autoconfigure`) | | Testcontainers service connection | `@Container` + `@DynamicPropertySource` | `@ServiceConnection` + `DynamicPropertyRegistrar` | | Kafka container | `KafkaContainer` | `ConfluentKafkaContainer` (from `org.testcontainers.kafka`) | ### No ObjectMapper Bean Needed Unlike early SB4 betas, the release version (4.0.3) does NOT require an explicit `ObjectMapper` bean in test configuration. Do not add one. ### Test Dependencies (webapp POM) ```xml org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-restclient test org.springframework.boot spring-boot-resttestclient test org.springframework.boot spring-boot-testcontainers test org.springframework.boot spring-boot-starter-security-test test ``` --- ## Surefire Configuration Root POM must configure both **surefire** (unit tests) and **failsafe** (integration tests) with JVM flags for Java 25 + Mockito: ### Surefire (unit tests) ```xml org.apache.maven.plugins maven-surefire-plugin ${surefire.version} @{argLine} -Djdk.instrument.traceUsage=false --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --enable-native-access=ALL-UNNAMED -javaagent:${org.mockito:mockito-core:jar} **/*IntegrationTest.java ``` Requires `maven-dependency-plugin` with `properties` goal so `${org.mockito:mockito-core:jar}` resolves. ### Failsafe (integration tests, in webapp POM) ```xml @{argLine} --enable-preview --enable-native-access=ALL-UNNAMED ``` --- ## Palantir Java Format Spotless with Palantir Java Format is auto-applied on `compile` phase. Never manually reformat. ```xml 2.81.0 false ``` Spotless includes: `src/main/java/**/*.java`, `src/test/java/**/*.java`, `src/test-integration/java/**/*.java`. --- ## No `var` Always use explicit types for local variables. Never use `var`. ```java Map flags = featureFlagService.getAllByInstanceId(instanceId); ``` --- ## Known Pitfalls These are common mistakes found in past scaffoldings. Avoid them: 1. **`@Import` on Application class**: `@SpringBootApplication` only scans its own base package. Cross-module `@Configuration` classes (security) **must** be explicitly imported via `@Import({...})`. Without this, tests pass (they use their own `@Import`) but production startup fails with missing beans. 2. **JwtTokenConverter null-safety**: When extracting JWT claims, always null-check **all** claims (including `scope`) before calling `.toString()`. Correct pattern: extract as `Object`, null-check, then convert. 3. **BaseIntegrationTest JWT expiration**: Use `Instant.now().plus(duration)` — **not** `Instant.now().plusSeconds(duration.toMillis())` which mixes units (millis treated as seconds = ~16.7h instead of 1min). 4. **Terraform `outputs.tf` in project module**: When using `terraform-aws-modules/ecs/aws//modules/service`, reference outputs from `module.ecs_service` (e.g. `module.ecs_service.name`, `module.ecs_service.tasks_iam_role_arn`) — **not** bare resources like `aws_ecs_service.xxx` which don't exist. 5. **Terraform DB module password escaping**: In `local-exec` provisioners using single-quoted bash strings, escape single quotes with the `'\''` pattern — **not** `\'` (backslash has no meaning inside single quotes in bash). 6. **TFLint unused declarations**: Do not declare `locals` or `variables` that are not referenced. When Kafka = No, omit `msk_cluster_name` variable entirely. Run `tflint --recursive` before pushing.