diff --git a/.devin/skills/java25-upgrade-milestone.md b/.devin/skills/java25-upgrade-milestone.md new file mode 100644 index 000000000..8cafbba76 --- /dev/null +++ b/.devin/skills/java25-upgrade-milestone.md @@ -0,0 +1,77 @@ +# Skill: Execute a Java 25 Upgrade Milestone + +Use this skill when asked to implement a specific milestone (2-10) of the Java 25 upgrade on the `java-25-target` branch. + +## Prerequisites + +- The `java-25-target` branch exists with `REVIEW.md` at the repo root +- All prior milestones have been merged into `java-25-target` +- The correct JDK is installed for the target milestone (see AGENTS.md for version mapping) + +## Procedure + +### Step 1: Read the milestone spec +``` +Read REVIEW.md and locate the milestone section you've been asked to implement. +Note the exact scope, files list, and review criteria checkboxes. +``` + +### Step 2: Read the agent guidelines +``` +Read AGENTS.md for branch naming, verification commands, coding standards, and common pitfalls. +``` + +### Step 3: Create a feature branch +```bash +git fetch origin +git checkout java-25-target +git pull origin java-25-target +git checkout -b devin/$(date +%s)-milestone-- +``` +Replace `` with the milestone number and `` with a kebab-case summary. + +### Step 4: Implement the changes +- Only modify files listed in the milestone scope +- Follow `.windsurf/rules/java-developer-guide.md` coding standards +- For Milestones 4+5: these MUST be combined in a single branch/PR since javax→jakarta won't compile without Spring Boot 3 + +### Step 5: Run verification +```bash +# Set JAVA_HOME appropriate for the milestone +export JAVA_HOME= + +# Format code +./gradlew spotlessJavaApply + +# Run full test suite +./gradlew clean test + +# Verify coverage threshold +./gradlew jacocoTestReport jacocoTestCoverageVerification + +# Run spotless check (should pass after apply) +./gradlew spotlessCheck +``` + +### Step 6: Validate review criteria +Go through each checkbox in the milestone's "Review criteria" section in REVIEW.md. Verify each one explicitly: +- Run the grep/search commands listed (e.g., `grep -r "org.joda.time" src/` for Milestone 3) +- Confirm version numbers in `build.gradle` match expectations +- Confirm `.java-version` and CI workflow versions match + +### Step 7: Commit and push +```bash +git add -A +git commit -m "milestone : " +git push origin HEAD +``` + +### Step 8: Create PR +- **Base branch:** `java-25-target` (NOT `main`) +- **Title:** `Milestone : ` +- **Body:** Include the review criteria checklist from REVIEW.md with each item checked off, plus a summary of what changed and any notable decisions + +### Step 9: Wait for CI +- Monitor CI checks on the PR +- If tests fail, fix issues and push additional commits +- Do not merge — wait for human review diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..7a17e4d59 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,90 @@ +# Agent Guidelines — Java 25 Upgrade + +This file provides context and rules for AI agents (Devin sessions) working on the `java-25-target` branch to incrementally upgrade this application from Java 11 + Spring Boot 2.6.3 to Java 25 + Spring Boot 3.5+. + +## Branch Strategy + +- **`main`** — stable, Java 11 + Spring Boot 2.6.3 baseline +- **`java-25-target`** — long-lived upgrade branch; all milestone PRs target this branch +- Each milestone is a separate PR into `java-25-target` +- Do NOT merge `java-25-target` back to `main` until all milestones are complete and reviewed + +## Milestone Execution Rules + +1. **Read `REVIEW.md` first.** It defines every milestone's scope, files, and review criteria. Do not deviate from its scope. +2. **One milestone per PR** (exception: milestones 4+5 are combined in a single commit/PR since the javax→jakarta change won't compile without Spring Boot 3). +3. **Never skip ahead.** Milestones must be completed in order (2 → 3 → 4+5 → 6 → 7 → 8 → 9 → 10). Each milestone assumes the previous one is already merged. +4. **Branch naming:** `devin/-milestone--` (e.g., `devin/1718000000-milestone-2-spring-boot-27`). +5. **Base branch for PRs:** Always `java-25-target`, never `main`. + +## Verification Commands + +Run these after every milestone to validate the change: + +```bash +# Set Java home (adjust path if the environment blueprint provides a different JDK) +export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 # milestones 2-3 +# export JAVA_HOME= # milestones 4-7 +# export JAVA_HOME= # milestones 8-10 + +# Build and test +./gradlew clean test + +# Spotless formatting check +./gradlew spotlessCheck + +# Apply formatting if needed +./gradlew spotlessJavaApply + +# JaCoCo coverage (must be ≥ 80%) +./gradlew jacocoTestReport jacocoTestCoverageVerification +``` + +## Coding Standards + +All code must comply with `.windsurf/rules/java-developer-guide.md`. Key rules: +- **PascalCase** for classes, **camelCase** for methods/variables, **ALL_CAPS** for constants +- **Constructor injection** — never use `@Autowired` on fields +- **JUnit 5** for all tests (the project uses `useJUnitPlatform()`) +- **Google Java Format** via Spotless — always run `spotlessCheck` before committing +- Refer to `.windsurf/rules/frontend-style-guide.md` for any frontend changes (Next.js/React in `frontend/`) + +## Project Architecture + +``` +src/main/java/io/spring/ +├── api/ # REST controllers, security filters, exception handlers +├── application/ # CQRS query services, DTOs, command services, validators +├── core/ # Domain entities (Article, Comment, User, Tag, FollowRelation) +├── graphql/ # Netflix DGS GraphQL datafetchers and mutations +├── infrastructure/ # MyBatis mappers, JWT service, repository implementations +└── JacksonCustomizations.java # JSON serialization config +``` + +Key technology choices: +- **Persistence:** MyBatis (not JPA) with XML mappers in `src/main/resources/mapper/` +- **Database:** SQLite (`dev.db` is ephemeral — deleted on `clean`/`bootRun`) +- **Migrations:** Flyway (`src/main/resources/db/migration/`) +- **GraphQL:** Netflix DGS with SDL in `src/main/resources/schema/` +- **Auth:** JWT via JJWT library, custom `Token` header prefix (not `Bearer`) +- **Testing:** JUnit 5 + MockMvc + RestAssured; Selenium E2E tests are separate (`seleniumTest` task) + +## Dependency Notes + +- `joda-time` → remove in Milestone 3, replace with `java.time` +- `javax.*` → `jakarta.*` in Milestones 4+5 (except `javax.crypto` which stays — it's in `java.base`) +- `mockito-inline` → remove in Milestone 6 (inline mocking is default in mockito-core 5.x) +- JJWT 0.11 → 0.12 API migration in Milestone 7 (builder methods renamed) + +## Common Pitfalls + +1. **SQLite + Flyway:** The `dev.db` file is deleted on `clean` and `bootRun`. Don't rely on persisted database state across test runs. +2. **DGS codegen:** The `generateJava` task produces code in `build/generated/`. Don't edit generated files — modify the SDL schema in `src/main/resources/schema/` instead. +3. **JaCoCo threshold:** The build enforces 80% line coverage. If you remove code, make sure test coverage doesn't drop below the threshold. +4. **Selenium tests:** These are excluded from the standard `test` task. Only run them via `./gradlew seleniumTest` and only when a running app instance is available. +5. **Spring Security 6.x (Milestone 5):** The `WebSecurityConfigurerAdapter` is removed in Spring Security 6. You'll need to migrate to component-based security configuration with `SecurityFilterChain` beans. + +## Skills + +See `.devin/skills/` for reusable skill definitions: +- `java25-upgrade-milestone.md` — step-by-step procedure for executing any single milestone diff --git a/REVIEW.md b/REVIEW.md new file mode 100644 index 000000000..40feda520 --- /dev/null +++ b/REVIEW.md @@ -0,0 +1,213 @@ +# Java 25 Upgrade — Review Checkpoints + +This document defines the review checkpoints for the incremental upgrade from Java 11 + Spring Boot 2.6.3 to Java 25 + Spring Boot 3.5+. Each milestone must pass its review criteria before proceeding. + +## Coding Standards Reference + +All changes must comply with `.windsurf/rules/java-developer-guide.md`. Key enforcement points: +- PascalCase for classes, camelCase for methods/variables, ALL_CAPS for constants +- Constructor injection over field injection +- Spring Boot best practices and proper annotation usage +- JUnit 5 for all tests +- Spotless formatting (Google Java Format) must pass +- JaCoCo coverage ≥ 80% + +--- + +### Milestone 1: Branch Setup + REVIEW.md +**Scope:** Create branch, add this file +**Review criteria:** +- [ ] Branch `java-25-target` exists off `main` +- [ ] `REVIEW.md` present at repo root +- [ ] No functional code changes + +--- + +### Milestone 2: Spring Boot 2.6.3 → 2.7.x +**Scope:** `build.gradle` — update Spring Boot plugin and `io.spring.dependency-management` plugin version +**Files:** `build.gradle` +**Review criteria:** +- [ ] `org.springframework.boot` plugin version is 2.7.x (latest 2.7) +- [ ] `io.spring.dependency-management` plugin updated to compatible version +- [ ] `./gradlew clean test` passes +- [ ] Note any new deprecation warnings in build output (these guide Milestone 4/5) + +--- + +### Milestone 3: Joda-Time → java.time Migration +**Scope:** Replace all `org.joda.time.DateTime` usage with `java.time.Instant` or `java.time.LocalDateTime`. Remove `joda-time` dependency. +**Files to modify (all files containing Joda-Time DateTime):** +- `src/main/java/io/spring/core/article/Article.java` +- `src/main/java/io/spring/core/comment/Comment.java` +- `src/main/java/io/spring/application/data/ArticleData.java` +- `src/main/java/io/spring/application/data/CommentData.java` +- `src/main/java/io/spring/application/DateTimeCursor.java` +- `src/main/java/io/spring/application/ArticleQueryService.java` +- `src/main/java/io/spring/application/CommentQueryService.java` +- `src/main/java/io/spring/infrastructure/mybatis/DateTimeHandler.java` +- `src/main/java/io/spring/JacksonCustomizations.java` +- `src/main/java/io/spring/graphql/ArticleDatafetcher.java` +- `src/main/java/io/spring/graphql/CommentDatafetcher.java` +- `src/main/java/io/spring/infrastructure/mybatis/readservice/CommentReadService.java` +- `src/test/java/io/spring/TestHelper.java` +- `src/test/java/io/spring/application/article/ArticleQueryServiceTest.java` +- `src/test/java/io/spring/api/ArticleApiTest.java` +- `src/test/java/io/spring/api/ArticlesApiTest.java` +- `build.gradle` (remove `joda-time:joda-time:2.10.13`) + +**Review criteria:** +- [ ] Zero `org.joda.time` imports in codebase (`grep -r "org.joda.time" src/` returns nothing) +- [ ] `joda-time` dependency removed from `build.gradle` +- [ ] `DateTimeHandler` updated to handle `java.time` types for MyBatis +- [ ] `JacksonCustomizations` serializers updated for `java.time` +- [ ] All tests pass (`./gradlew clean test`) +- [ ] JaCoCo coverage ≥ 80% + +--- + +### Milestone 4: javax → jakarta Namespace Migration +**Scope:** Replace all `javax.validation` and `javax.servlet` imports with `jakarta.*` equivalents (`javax.crypto` stays — it's in `java.base`, not Jakarta EE). This is prep for Spring Boot 3 — the actual switch happens in Milestone 5 but the awareness of affected files is tracked here. NOTE: On Spring Boot 2.7, keep `javax.*` — this milestone documents the scope and may be merged with Milestone 5 if preferred. +**Files affected (21 files with javax imports):** +- `src/main/java/io/spring/api/security/JwtTokenFilter.java` (javax.servlet) +- `src/main/java/io/spring/application/user/UserService.java` (javax.validation) +- `src/main/java/io/spring/api/UsersApi.java` +- `src/main/java/io/spring/api/CommentsApi.java` +- `src/main/java/io/spring/api/ArticleApi.java` +- `src/main/java/io/spring/api/ArticlesApi.java` +- `src/main/java/io/spring/api/CurrentUserApi.java` +- `src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java` +- `src/main/java/io/spring/application/article/DuplicatedArticleConstraint.java` +- `src/main/java/io/spring/application/article/DuplicatedArticleValidator.java` +- `src/main/java/io/spring/application/article/ArticleCommandService.java` +- `src/main/java/io/spring/application/article/NewArticleParam.java` +- `src/main/java/io/spring/application/user/DuplicatedEmailConstraint.java` +- `src/main/java/io/spring/application/user/DuplicatedEmailValidator.java` +- `src/main/java/io/spring/application/user/DuplicatedUsernameConstraint.java` +- `src/main/java/io/spring/application/user/DuplicatedUsernameValidator.java` +- `src/main/java/io/spring/application/user/RegisterParam.java` +- `src/main/java/io/spring/application/user/UpdateUserParam.java` +- `src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java` +- `src/main/java/io/spring/graphql/UserMutation.java` +- `src/main/java/io/spring/infrastructure/service/DefaultJwtService.java` (javax.crypto) + +**Review criteria:** +- [ ] All `javax.validation` → `jakarta.validation` +- [ ] All `javax.servlet` → `jakarta.servlet` +- [ ] `javax.crypto` stays as-is (it's part of `java.base`, not Jakarta EE) +- [ ] `grep -r "javax\." src/main/java/ | grep -v "javax.crypto"` returns nothing +- [ ] Code compiles (will only compile after Spring Boot 3 dep is in place — combine with Milestone 5) + +--- + +### Milestone 5: Spring Boot 2.7 → 3.x + Java 17 +**Scope:** Major framework upgrade. Update Spring Boot plugin to 3.x, set Java 17, update CI. +**Files:** +- `build.gradle` — `org.springframework.boot` to 3.2+, `sourceCompatibility`/`targetCompatibility` to '17' +- `.java-version` — change from `11` to `17` +- `.github/workflows/gradle.yml` — change `java-version: '11'` to `'17'` +- Security configuration may need updates for Spring Security 6.x changes + +**Review criteria:** +- [ ] `sourceCompatibility = '17'` and `targetCompatibility = '17'` in build.gradle +- [ ] `.java-version` reads `17` +- [ ] CI workflow uses `java-version: '17'` +- [ ] Spring Boot 3.x plugin version in `build.gradle` +- [ ] Application boots (`./gradlew bootRun` starts without error) +- [ ] All tests pass +- [ ] No `javax.*` imports remain (except `javax.crypto`) + +--- + +### Milestone 6: Dependency Version Bumps (Spring Boot 3 Compatibility) +**Scope:** Update all dependencies to Spring Boot 3 / Java 17+ compatible versions +**File:** `build.gradle` +**Dependencies to update:** + +| Dependency | From | To | +|---|---|---| +| `com.netflix.dgs.codegen` plugin | 5.0.6 | 6.x+ (Spring Boot 3 compatible) | +| `com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter` | 4.9.21 | 7.x+ | +| `org.mybatis.spring.boot:mybatis-spring-boot-starter` | 2.2.2 | 3.x | +| `io.jsonwebtoken:jjwt-*` | 0.11.2 | 0.12.x | +| `io.rest-assured:*` | 4.5.1 | 5.x | +| `org.mockito:mockito-inline` | 4.0.0 | Remove (use `mockito-core` 5.x, inline is default) | +| `com.diffplug.spotless` plugin | 6.2.1 | Latest 6.x | +| JaCoCo `toolVersion` | 0.8.7 | 0.8.11+ | +| `org.xerial:sqlite-jdbc` | 3.36.0.3 | Latest | +| Selenium / WebDriverManager / TestNG | current | Latest compatible | + +**Review criteria:** +- [ ] All dependencies resolve without conflicts +- [ ] `./gradlew clean test` passes +- [ ] `./gradlew spotlessCheck` passes +- [ ] JaCoCo coverage ≥ 80% +- [ ] No dependency vulnerability warnings + +--- + +### Milestone 7: JJWT API Migration +**Scope:** Update `DefaultJwtService.java` to use jjwt 0.12 builder API (deprecated methods in 0.11) +**File:** `src/main/java/io/spring/infrastructure/service/DefaultJwtService.java` +**Changes:** +- `Jwts.builder().setSubject()` → `Jwts.builder().subject()` +- `Jwts.parserBuilder().setSigningKey().build().parseClaimsJws()` → `Jwts.parser().verifyWith().build().parseSignedClaims()` +- `claimsJws.getBody()` → `claimsJws.getPayload()` +- `SignatureAlgorithm` enum → `Jwts.SIG.HS512` + +**Review criteria:** +- [ ] No deprecated jjwt API calls +- [ ] JWT token generation and validation still works (existing tests pass) +- [ ] No `SignatureAlgorithm` enum usage + +--- + +### Milestone 8: Java 17 → 25 + Spring Boot 3.5 +**Scope:** Runtime upgrade to Java 25 LTS +**Files:** +- `build.gradle` — `sourceCompatibility`/`targetCompatibility` to '25', Spring Boot to 3.5+ +- `.java-version` — `25` +- `.github/workflows/gradle.yml` — `java-version: '25'` + +**Review criteria:** +- [ ] `sourceCompatibility = '25'` in build.gradle +- [ ] `.java-version` reads `25` +- [ ] CI uses `java-version: '25'` +- [ ] `./gradlew clean test` passes +- [ ] Application boots successfully +- [ ] No reflection/illegal-access warnings at runtime + +--- + +### Milestone 9: Modernize Java Idioms +**Scope:** Adopt Java 17-25 language features per coding guide +**Changes across all source files:** +- Convert data classes to `record` types where appropriate (e.g., param classes, data classes) +- Use pattern matching for `instanceof` +- Use text blocks for multi-line strings +- Use `sealed` classes/interfaces where inheritance is constrained +- Replace field injection with constructor injection (per coding guide) +- Use `var` for local variables where type is obvious + +**Review criteria:** +- [ ] No field injection (`@Autowired` on fields) — use constructor injection +- [ ] Param/data classes evaluated for `record` conversion +- [ ] Pattern matching used for `instanceof` checks +- [ ] All tests pass +- [ ] Spotless formatting passes +- [ ] JaCoCo coverage ≥ 80% + +--- + +### Milestone 10: Final Cleanup + Coding Standards Audit +**Scope:** Full codebase audit against `.windsurf/rules/java-developer-guide.md` +**Review criteria:** +- [ ] `./gradlew spotlessCheck` passes +- [ ] `./gradlew clean test` passes with JaCoCo ≥ 80% +- [ ] No deprecated API usage in source code +- [ ] Naming conventions enforced (PascalCase classes, camelCase methods, ALL_CAPS constants) +- [ ] No `javax.*` imports (except `javax.crypto`) +- [ ] No Joda-Time usage +- [ ] No jjwt deprecated API +- [ ] Constructor injection throughout +- [ ] `.windsurf/rules/java-developer-guide.md` updated to reference Java 25 (instead of "Java 17 or later") +- [ ] README.md updated to reflect new Java/Spring Boot versions