Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .devin/skills/java25-upgrade-milestone.md
Original file line number Diff line number Diff line change
@@ -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-<N>-<short-description>
```
Replace `<N>` with the milestone number and `<short-description>` 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=<path-to-correct-jdk>

# 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 <N>: <description matching REVIEW.md scope>"
git push origin HEAD
```

### Step 8: Create PR
- **Base branch:** `java-25-target` (NOT `main`)
- **Title:** `Milestone <N>: <scope summary>`
- **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
90 changes: 90 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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/<timestamp>-milestone-<N>-<short-description>` (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=<java-17-path> # milestones 4-7
# export JAVA_HOME=<java-25-path> # 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
213 changes: 213 additions & 0 deletions REVIEW.md
Original file line number Diff line number Diff line change
@@ -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
Loading