# 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.<project>.<module>;

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.<project>")
@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.<project>` (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
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-restclient</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-resttestclient</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security-test</artifactId>
    <scope>test</scope>
</dependency>
```

---

## 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
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${surefire.version}</version>
    <configuration>
        <argLine>@{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}</argLine>
        <excludes>
            <exclude>**/*IntegrationTest.java</exclude>
        </excludes>
    </configuration>
</plugin>
```

Requires `maven-dependency-plugin` with `properties` goal so `${org.mockito:mockito-core:jar}` resolves.

### Failsafe (integration tests, in webapp POM)

```xml
<argLine>@{argLine} --enable-preview --enable-native-access=ALL-UNNAMED</argLine>
```

---

## Palantir Java Format

Spotless with Palantir Java Format is auto-applied on `compile` phase. Never manually reformat.

```xml
<palantirJavaFormat>
    <version>2.81.0</version>
    <style>PALANTIR</style>
    <formatJavadoc>false</formatJavadoc>
</palantirJavaFormat>
```

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<String, String> 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.
