Files
llm-wiki/raw/errors/spring-boot-four-jackson-three-migration-2026-06-30.md

297 lines
25 KiB
Markdown

---
title: error / spring-boot-four-jackson-three-migration-2026-06-30
source_type: error-note
status: raw
related_branches: []
related_projects: [ca-skeleton-operational-contract]
tags: [error, ca-tmpl, runtime, spring-boot, json, testing, distributed-lock]
created: 2026-06-30
status_label: resolved
---
# error: spring-boot-four-jackson-three-migration-2026-06-30
> Layer: `raw/errors/` — Spring Boot 4 / Jackson 3 / Testcontainers 2 / Spring Integration 7 migration 중 발생한 실패 묶음의 트러블슈팅 기록.
## Parent / 부모
- [[raw/project-notes/ca-skeleton-operational-contract]]
- 작업 식별자: `chore-spring-boot-four-migration` (별도 branch-note 없음)
## 증상 / Symptom
- 에러 메시지 (원문 그대로):
```text
Property 'server.error.include-stacktrace' is Deprecated: Use 'spring.web.error.include-stacktrace' instead.vscode-spring-boot(YAML_DEPRECATED_ERROR)
```
- 에러 메시지 (원문 그대로):
```text
server.error.include-stacktrace -> spring.web.error.include-stacktrace
Default: never
Deprecated!
When to include the "trace" attribute.
```
- 에러 메시지 (원문 그대로):
```text
MethodName must match pattern '^[a-z][a-zA-Z0-9]*$'
```
- 에러 메시지 (원문 그대로):
```text
ConstantName must match pattern
NeedBraces
StaticVariableName
```
- 에러 메시지 (원문 그대로):
```text
The method asText() from the type JsonNode is deprecatedJava(67108967)
String tools.jackson.databind.JsonNode.asText()
Deprecated. Use asString() instead.
Source: jackson-databind-3.0.2.jar
```
- 에러 메시지 (원문 그대로):
```text
Execution failed for task ':adapter-web:spotlessJavaCheck'.
The following files had format violations:
Run './gradlew spotlessApply' to fix all violations.
```
- 에러 메시지 (원문 그대로):
```text
Execution failed for task ':app-bootstrap:compileTestJava'.
bad class file: /home/donghyeon/workspace/ca-tmpl/src/app-bootstrap/build/classes/java/test/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.class
unable to access file: java.nio.file.NoSuchFileException
Please remove or make sure it appears in the correct subdirectory of the classpath.
```
- 에러 메시지 (원문 그대로):
```text
H S SECUJDES: Unsafe Jackson deserialization configuration used in
dev.caskeleton.bootstrap.architecture.violations.boundary.DefaultTypingFixture.unsafe()
SpotBugs ended with exit code 1
```
- 에러 메시지 (원문 그대로):
```text
java.lang.Error: Failed Approval
Approved: .../EnvelopeContractTest.successEnvelopeShape.approved.txt
Received: .../EnvelopeContractTest.successEnvelopeShape.received.txt
```
- 경고 메시지 (원문 그대로):
```text
Assigning String value 'high' to property of enum type 'com.github.spotbugs.snom.Confidence'.
This behavior has been deprecated. This will fail with an error in Gradle 10.
```
- 경고 메시지 (원문 그대로):
```text
Invocation of Task.project at execution time has been deprecated.
This will fail with an error in Gradle 10.
```
- 경고 메시지 (요약):
```text
MissingJavadocMethodCheck 310
MissingJavadocTypeCheck 34
```
- CI 실패 메시지 후보 (workflow 원문):
```text
A governed contract baseline changed but the PR lacks the
'intent:breaking-change-approved' label.
```
- 서버 시작 실패 메시지 (원문 발췌):
```text
Caused by: org.flywaydb.core.api.exception.FlywayValidateException:
Validate failed: Migrations have failed validation
Migration checksum mismatch for migration version 4
-> Applied to database : 1718831886
-> Resolved locally : -37693890
Either revert the changes to the migration, or run repair to update the schema history.
```
- 서버 시작 실패 메시지 (local `bootRun`):
```text
Failed to bind properties under 'spring.profiles.active' to java.util.Set<java.lang.String>
Value: "${SPRING_PROFILES_ACTIVE}"
Profile '${SPRING_PROFILES_ACTIVE}' must contain a letter, digit or allowed char
```
- 서버 시작 실패 메시지 (local `bootRun`):
```text
Could not initialize Logback logging from classpath:logback-spring.xml
Could not resolve placeholder 'APP_NAME' in value "${APP_NAME}"
```
- 서버 시작 실패 메시지 (사용자 신규 로그):
```text
org.flywaydb.core.api.FlywayException: Found more than one migration with version 4
Offenders:
-> .../adapter-persistence-postgresql-0.0.1+3a300d89ec30.jar!/db/migration/postgresql/V4__int_lock.sql
-> .../adapter-persistence-postgresql/build/resources/main/db/migration/postgresql/V4__int_lock.sql
```
- 발생 컨텍스트: Spring Boot 4 dependency set에서 Gradle compile/test/check 및 VSCode Spring Boot YAML validation 실행.
- 발생 시점: 2026-06-30
- 발생 환경: local
- 재현 가능 여부: `always`
## 재현 절차 / Reproduction
1. Spring Boot 4 / Spring Framework 7 / Jackson 3 dependency set으로 프로젝트를 refresh한다.
2. `src/app-bootstrap/src/test/resources/application-test.yml` 또는 application yml에서 deprecated `server.error.include-stacktrace` key를 유지한다.
3. VSCode Spring Boot YAML validation 또는 Gradle test/check를 실행한다.
4. 기대 결과: 설정 key, compile surface, tests가 현 dependency set과 일치한다.
5. 실제 결과: deprecated YAML warning/error, moved package compile errors, Jackson runtime test failures, JDBC lock schema/API mismatch, Checkstyle method naming failures가 발생한다.
## 조사 단계 / Investigation log
- 2026-06-30 — `rg -n "server\\.error\\.include"` 실행 → repository config에 deprecated key가 남아 있음을 확인.
- 2026-06-30 — Spring Boot 4.0.0 jar metadata에서 replacement 확인 → `spring.web.error.include-stacktrace`와 `spring.web.error.include-message`로 이동.
- 2026-06-30 — focused adapter-web/app-bootstrap/sample tests 실행 → Jackson 3 `tools.jackson.*` API와 local JsonNullable module 필요 확인.
- 2026-06-30 — Spring Integration 7 source/API 확인 → `JdbcLockRegistry` TTL constructor와 `DistributedLock.tryLock(wait, ttl)` path 필요 확인.
- 2026-06-30 — `./gradlew test --continue` 실행 → 전체 test suite 통과 확인.
- 2026-06-30 — `./gradlew check` 실행 → architecture/env checks는 통과했으나 Checkstyle/Spotless 단계에서 test method naming 위반으로 실패 확인.
- 2026-07-01 — `rg -n "\\.asText\\(" src` 실행 → adapter-web auth 테스트 2개 파일에서 Jackson 3 deprecated call 확인.
- 2026-07-01 — adapter-web auth focused tests 실행 → `asString()` 전환 후 테스트 통과 확인.
- 2026-07-01 — `./gradlew check --continue` 실행 → blocking failures가 `adapter-web`, `app-bootstrap`, `sample-portfolio` Spotless format drift였음을 확인.
- 2026-07-01 — `./gradlew spotlessApply` 실행 → formatter-owned import order / google-java-format drift 정리.
- 2026-07-01 — `./gradlew check` 재실행 → `app-bootstrap:compileTestJava`가 stale `OutboxContainerTestSupport.class` 경로를 참조하며 실패.
- 2026-07-01 — `./gradlew :app-bootstrap:cleanTest :app-bootstrap:compileTestJava` 실행 → stale test output 제거 후 재컴파일 성공.
- 2026-07-01 — Checkstyle XML report를 집계 → `MethodName`, `ConstantName`, `NeedBraces`, `StaticVariableName` error entries가 test source에 남아 있음을 확인.
- 2026-07-01 — test method/constant bulk rename 및 one-line `if` brace cleanup 실행 → `./gradlew checkstyleTest checkstyleSampleOffTest --continue` 후 XML parser `total_errors=0` 확인.
- 2026-07-01 — ApprovalTests failed path 확인 → `PackageSettings.UseApprovalSubdirectory` exact field convention을 lowerCamelCase로 바꾸면 approved snapshot directory lookup이 깨짐을 확인.
- 2026-07-01 — SpotBugs `SECUJDES` output 확인 → architecture negative fixture가 의도적으로 unsafe Jackson call을 포함해 false-positive처럼 출력됨을 확인.
- 2026-07-01 — ApprovalTests snapshot filename을 lowerCamelCase method name과 맞추고, exact field/negative fixture만 targeted suppression/filter 적용.
- 2026-07-01 — `./gradlew build --warning-mode all` 실행 → Gradle 10 deprecation 후보가 SpotBugs enum coercion과 task action project lookup 2건임을 확인.
- 2026-07-01 — Checkstyle XML report 집계 → 남은 warning이 `MissingJavadocMethodCheck` 310건, `MissingJavadocTypeCheck` 34건뿐임을 확인.
- 2026-07-01 — SpotBugs `reportLevel`을 enum value로 넘기고 `verifyCleanArchitectureDependencies` lookup을 `rootProject.project(...)`로 변경.
- 2026-07-01 — default Checkstyle에서 Javadoc warning-tier modules를 제거 → Checkstyle XML warning/error total 0 확인.
- 2026-07-01 — CI quality-gates workflow 검토 → PR 전용 breaking-change-approval gate가 `.approved.txt` filename-only rename도 label-required로 볼 수 있음을 확인.
- 2026-07-01 — old/new ApprovalTests approved snapshot SHA 비교 → 세 snapshot 모두 내용 동일, 파일명만 변경됨을 확인.
- 2026-07-01 — `.github/scripts/verify-breaking-change-approval.sh` 추가 → `R100` identical-content rename은 통과, content change는 label 없으면 실패하도록 분리.
- 2026-07-01 — temp git repo에서 no-change / identical rename / content change without label / content change with label path 검증.
- 2026-07-01 — `./gradlew check`, `./gradlew build` 실행 → 둘 다 success.
- 2026-07-01 — 사용자 서버 startup log 확인 → 실제 root cause는 후속 `BeanCreationException`이 아니라 마지막 `Caused by`의 Flyway V4 checksum mismatch임을 확인.
- 2026-07-01 — `git show b3bd7fa:.../V4__int_lock.sql` 확인 → 기존 V4에는 `EXPIRED_AFTER`가 없고 최근 커밋에서 V4에 컬럼을 직접 추가했음을 확인.
- 2026-07-01 — `FlywayMigrationCompatibilityContractTest` 추가 → old V4가 이미 적용된 PostgreSQL DB에 current V4/V5 migration set을 적용하는 시나리오를 재현.
- 2026-07-01 — `V4__int_lock.sql`에서 `EXPIRED_AFTER`를 제거하고 `V5__int_lock_expired_after.sql`을 추가 → focused migration compatibility test 통과.
- 2026-07-01 — 순수 `bootRun` 실행 → profile placeholder literal binding failure 확인.
- 2026-07-01 — `spring.profiles.active`에 `local` fallback과 `EnvProfileMatrixContractTest` 회귀 테스트 추가 → focused test 통과.
- 2026-07-01 — 순수 `bootRun` 재실행 → logback early placeholder failure 확인.
- 2026-07-01 — `logback-spring.xml` springProperty source를 direct env key로 변경하고 `StructuredLogFieldContractTest` 회귀 테스트 추가 → focused test 통과.
- 2026-07-01 — `.env`를 process env로 명시 주입한 `bootRun` 실행 → 서버가 `Started CaSkeletonApplication`까지 도달하고 기존 DB에 V5 migration이 적용됨을 확인.
- 2026-07-01 — Gradle `bootRun`이 `src/.env`를 Java process env로 주입하도록 변경 → 순수 `timeout 60s ./gradlew :app-bootstrap:bootRun --no-daemon --stacktrace`도 `Started CaSkeletonApplication`까지 도달.
- 2026-07-01 — `./gradlew check verifyPublicPathSnapshot --no-daemon --stacktrace` 재실행 → success.
- 2026-07-01 — 사용자 신규 startup log 확인 → root cause가 이전 checksum mismatch가 아니라 stale `adapter-persistence-postgresql` JAR와 current resources의 Flyway migration duplicate version임을 확인.
- 2026-07-01 — `find src/adapter-persistence-postgresql/build/libs -name 'adapter-persistence-postgresql-*.jar'` 실행 → `0.0.1+3a300d89ec30.jar` 포함 다수의 old git-revision JAR가 남아 있음을 확인.
- 2026-07-01 — `verifyNoStaleTraceableJars`를 먼저 추가하고 실행 → 10개 module의 stale traceable JAR를 감지하며 실패해 검증이 실제 문제를 잡는 것을 확인.
- 2026-07-01 — `cleanStaleTraceableJars`, `verifyNoStaleTraceableJars`, `Jar`/`BootJar` 실행 전 stale archive cleanup을 추가하고 `check`에 연결.
- 2026-07-01 — `./gradlew verifyNoStaleTraceableJars --no-daemon --stacktrace` 재실행 → stale archive 105개 삭제 후 success.
- 2026-07-01 — `find src -path '*/build/libs/*.jar'` 실행 → 각 module에 current git-revision archive만 남은 것을 확인.
- 2026-07-01 — `timeout 45s ./gradlew :app-bootstrap:bootRun --no-daemon --stacktrace` 실행 → Flyway duplicate 오류 없이 `Started CaSkeletonApplication`까지 도달.
- 2026-07-01 — `./gradlew check verifyPublicPathSnapshot --no-daemon --stacktrace` 재실행 → new stale-jar gate 포함 success.
## 근본 원인 / Root cause
- 직접 원인: Spring Boot 4 / Spring Framework 7 / Jackson 3 / Testcontainers 2 / Spring Integration 7에서 package, module, configuration key, serializer API, lock schema/API가 변경되었는데 기존 코드와 설정이 Boot 3/Jackson 2 계열 surface에 남아 있었다.
- 근본 원인: patch upgrade와 달리 major upgrade는 compiler output뿐 아니라 runtime auto-configuration metadata, test-slice module split, third-party module compatibility까지 함께 바뀐다.
- 추가 원인: warning-only static-analysis task라도 rule severity가 `error`면 Gradle 출력에 error처럼 보이는 로그가 남는다. 이 경우 ignoreFailures 정책은 exit code만 완화하고 리포트의 severity를 바꾸지 않는다.
- 추가 원인: Gradle warning은 default mode에서 요약만 보이고 실제 제거 지점은 `--warning-mode all`에서만 드러난다. Checkstyle Javadoc warning은 meaningful documentation 없이 대량 주석을 강제하는 baseline이라 기본 build signal로 적합하지 않았다.
- 추가 원인: CI breaking-change approval gate가 content diff가 아니라 path glob 중심으로 governed snapshot 변경을 판단해, 동일 내용 rename도 breaking change로 오탐할 수 있었다.
- 추가 원인: 적용된 Flyway versioned migration인 `V4__int_lock.sql`에 Spring Integration 7용 `EXPIRED_AFTER` 컬럼을 직접 추가해 기존 DB의 `flyway_schema_history` checksum과 소스 checksum이 달라졌다.
- 추가 원인: Boot 4 early profile/logging initialization은 `spring-dotenv`가 `.env`를 Spring Environment에 넣기 전에 실행될 수 있어, required placeholder가 literal 또는 unresolved 상태로 실패했다.
- 추가 원인: Gradle `bootRun`은 working directory만 `src`로 바꿨고, `.env` 값을 Java process environment로 직접 주입하지는 않았다.
- 추가 원인: traceable artifact 이름에 git revision이 포함되는데 `build/libs`에 old revision JAR가 누적되었다. IDE/runtime classpath가 stale JAR와 current `build/resources/main`을 함께 잡으면 Flyway가 동일 versioned migration을 두 번 발견한다.
- 트리거 조건: Boot 4 dependency set refresh 후 Gradle compile/test/check 및 IDE YAML validation 실행.
## Sources / 근거 (해결 근거가 된 자료, 최소 1개+ 권장)
- [[raw/official-docs/spring-boot-test-slices-webmvctest-datajpatest-official]] — Boot test slice/Jackson component scan semantics 확인.
- [[raw/official-docs/lock-spring-integration-lock-registry]] — LockRegistry/JdbcLockRegistry와 TTL 만료 위험 확인.
- [[raw/official-docs/config-spring-boot-externalized-configuration]] — Spring Boot 설정 검증 관점 확인.
- [[raw/official-docs/tracing-spring-boot-3-actuator-tracing-reference]] — Micrometer tracing/OpenTelemetry starter 선택지 확인.
- local Spring Boot 4.0.0 configuration metadata — `server.error.include-*` replacement 확인.
## 해결 / Resolution
- 적용한 조치:
- Boot 4 moved package imports와 split module dependencies를 갱신했다.
- Jackson 3 `tools.jackson.*` API로 production/test code를 전환했다.
- Jackson 2 기반 nullable module 대신 local Jackson 3 `JsonNullable` serializer/deserializer module을 구성했다.
- Testcontainers PostgreSQL package를 Testcontainers 2 module namespace로 갱신했다.
- Spring Integration 7 JDBC lock schema에 `EXPIRED_AFTER`를 추가하고 TTL-aware lock API를 사용했다.
- `server.error.include-stacktrace/message`를 `spring.web.error.include-stacktrace/message`로 이전하고 env registry를 갱신했다.
- Jackson 3.0.2에서 deprecated 된 `JsonNode.asText()` 테스트 assertion을 `asString()`으로 변경했다.
- `spotlessApply`로 formatter-owned drift를 정리했다.
- `:app-bootstrap:cleanTest :app-bootstrap:compileTestJava`로 stale test compile output을 재생성했다.
- test method `snake_case`를 lowerCamelCase로, static constant를 `UPPER_SNAKE_CASE`로 정리했다.
- Checkstyle `NeedBraces` 위반이 남은 single-line `if`에 braces를 추가했다.
- ApprovalTests approved snapshot 파일명을 새 lowerCamelCase method name과 맞췄다.
- ApprovalTests reflection convention인 `UseApprovalSubdirectory`만 file-scoped Checkstyle exception으로 문서화했다.
- architecture negative fixture의 `SECUJDES`는 `DefaultTypingFixture.java` source에 한정해 SpotBugs exclude했다.
- SpotBugs Gradle `reportLevel`은 문자열 coercion 대신 `com.github.spotbugs.snom.Confidence.valueOf('HIGH')`를 사용했다.
- `verifyCleanArchitectureDependencies` task action 내부 project lookup은 `rootProject.project(...)`로 변경했다.
- Checkstyle default ruleset에서 `MissingJavadocType`, `MissingJavadocMethod`, `NonEmptyAtclauseDescription` warning-tier modules를 제거했다.
- breaking-change approval workflow inline shell을 `.github/scripts/verify-breaking-change-approval.sh`로 분리하고, `git diff --name-status --find-renames=100%` 기반으로 identical-content rename을 통과시켰다.
- 이미 적용된 `V4__int_lock.sql`을 원래 checksum으로 되돌리고 `V5__int_lock_expired_after.sql` 전진 migration으로 `EXPIRED_AFTER`를 추가했다.
- 기존 V4가 적용된 DB도 repair 없이 V5로 올라가는 Testcontainers 회귀 테스트를 추가했다.
- `spring.profiles.active`에 `local` fallback을 추가하고 early profile binding 계약 테스트를 추가했다.
- `logback-spring.xml`의 early properties를 `application.yml` 경유가 아니라 direct env key + default로 읽게 바꿨다.
- Gradle `bootRun`이 `src/.env`를 Java process env로 주입하되 이미 export된 env를 덮어쓰지 않게 했다.
- `cleanStaleTraceableJars`와 `verifyNoStaleTraceableJars`를 추가해 old git-revision JAR를 삭제하고 남아 있으면 검증 실패하도록 했다.
- `verifyNoStaleTraceableJars`를 기본 `check`에 연결했다.
- 모든 `Jar`/`BootJar` 계열 archive task 실행 전에 같은 archive base/classifier의 old git-revision JAR를 삭제하게 했다.
- 검증 방법:
- `./gradlew test --continue` success.
- `./gradlew verifyCleanArchitectureDependencies verifyEnvKeys verifyPublicPathSnapshot` success.
- `./gradlew checkstyleTest checkstyleSampleOffTest --continue` success and Checkstyle XML `total_errors=0`.
- `./gradlew checkstyleMain checkstyleTest checkstyleSampleOffTest --continue` success and Checkstyle XML total warning/error `0`.
- `./gradlew :app-bootstrap:spotbugsTest :app-bootstrap:spotbugsSampleOffTest` success without `SECUJDES` output.
- `./gradlew build --warning-mode all` success without Gradle deprecation output.
- `./gradlew check verifyPublicPathSnapshot --no-daemon --stacktrace` success.
- `./gradlew :app-bootstrap:sampleOffTest verifyCleanArchitectureDependencies --no-daemon --stacktrace` success.
- `bash .github/scripts/verify-gate-matrix.sh && bash .github/scripts/verify-supply-chain-contract.sh && bash .github/scripts/test-supply-chain-scripts.sh` success.
- temp git repo script test success for breaking-change approval paths.
- `./gradlew check` success.
- `./gradlew build` success.
- `./gradlew :app-bootstrap:test --tests dev.caskeleton.bootstrap.integration.FlywayMigrationCompatibilityContractTest --no-daemon --stacktrace` success.
- `./gradlew :app-bootstrap:test --tests dev.caskeleton.bootstrap.contract.EnvProfileMatrixContractTest --no-daemon --stacktrace` success.
- `./gradlew :app-bootstrap:test --tests dev.caskeleton.bootstrap.contract.StructuredLogFieldContractTest --no-daemon --stacktrace` success.
- `timeout 60s ./gradlew :app-bootstrap:bootRun --no-daemon --stacktrace` → process는 timeout 124로 종료했지만 로그에서 `Started CaSkeletonApplication` 및 Readiness `ACCEPTING_TRAFFIC` 확인.
- `./gradlew check verifyPublicPathSnapshot --no-daemon --stacktrace` success.
- `./gradlew verifyNoStaleTraceableJars --no-daemon --stacktrace` → stale traceable JAR 삭제 후 success.
- `find src -path '*/build/libs/*.jar'` → old git-revision JAR 제거 확인.
- `timeout 45s ./gradlew :app-bootstrap:bootRun --no-daemon --stacktrace` → process는 timeout 124로 종료했지만 로그에서 Flyway validate/migrate와 `Started CaSkeletonApplication` 확인.
- `./gradlew check verifyPublicPathSnapshot --no-daemon --stacktrace` success, `verifyNoStaleTraceableJars` 포함.
- `rg -n "server\\.error\\.include|spring\\.jackson\\.generator|SPRING_JACKSON_GEN_WRITE_BIGDECIMAL_AS_PLAIN" .`로 deprecated/removed keys 제거 확인.
- `rg -n "\\.asText\\(" src` no matches.
- `./gradlew :adapter-web:test --tests 'dev.caskeleton.adapter.web.auth.EnvelopeAccessDeniedHandlerTest' --tests 'dev.caskeleton.adapter.web.auth.EnvelopeAuthenticationEntryPointTest'` success.
- 잔여 위험 / 후속 작업:
- 기본 빌드의 Javadoc warning은 제거했지만, public API documentation 자체가 완료된 것은 아니다.
- tracing fallback은 local test contract를 유지하기 위한 최소 bean 구성이다. 운영 exporter 구성은 별도 작업으로 분리해야 한다.
- 대규모 test method rename으로 외부 IDE run configuration이나 문서가 old snake_case method name을 직접 참조하면 갱신이 필요하다.
- remote Gitea CI 로그는 로컬에 `gh`가 없고 GitHub remote가 아니어서 직접 조회하지 못했다. remote runner 재실행으로 최종 확인 필요.
- 누군가 잘못된 V4 내용으로 `flyway repair`를 이미 실행한 DB는 이번 V4 원복 후 반대 방향 checksum mismatch가 날 수 있다. 해당 경우에는 DB별 schema history 확인 후 별도 repair/backout 절차가 필요하다.
- `.env` parser는 단순 `KEY=value` 형식만 처리한다. quoted value, escaped newline, `export KEY=value`가 필요하면 확장해야 한다.
- IDE run configuration이 삭제된 old JAR absolute path를 직접 고정하고 있다면 IDE classpath refresh가 필요하다. Gradle `check`와 archive task는 stale JAR를 다시 만들지 않도록 막지만 IDE 설정 자체의 old path 참조까지 수정하지는 않는다.
## 회고 / Lessons
- 빨리 감지하는 신호:
- Boot major upgrade 후 `YAML_DEPRECATED_ERROR`, `tools.jackson`/`com.fasterxml` 혼재, `JsonNode.asText()` deprecation, `NoSuchMethod`/schema mismatch, Testcontainers package missing이 함께 보이면 단순 import fix가 아니라 migration surface 전체를 점검해야 한다.
- 예방 체크리스트 항목 후보:
- Boot metadata replacement grep.
- Jackson 2 module compatibility audit.
- Testcontainers module namespace audit.
- Spring Integration schema/API diff audit.
- `test --continue`와 `check`를 분리해 test pass와 style gate failure를 별도 보고.
- Spotless failure가 보이면 수동 import/order patch보다 `spotlessApply`로 formatter-owned 영역을 정규화.
- `bad class file` + `NoSuchFileException` 조합은 stale Gradle test output 가능성이 크므로 해당 source set clean 후 재컴파일.
- ApprovalTests `PackageSettings`처럼 reflection convention을 쓰는 도구 설정은 일반 naming cleanup 전에 exact symbol contract인지 먼저 확인한다.
- warning-only 정적분석 task라도 developer-facing error log를 줄이려면 XML severity entry를 0으로 만드는 별도 검증이 필요하다.
- Gradle deprecation은 `--warning-mode all`을 정기적으로 돌려 실제 Gradle 10 failure 후보를 조기에 제거한다.
- Javadoc은 대량 기계 주석으로 해결하지 말고, 공개 API 문서화 정책과 범위를 별도 작업으로 잡는 편이 낫다.
- Contract snapshot 게이트는 파일 경로 변경과 내용 변경을 분리해야 한다. ApprovalTests method rename은 filename drift를 만들지만 wire contract drift를 뜻하지 않는다.
- Flyway versioned migration은 한 번 적용되면 코드 리뷰에서도 immutable artifact로 취급해야 한다. schema drift는 새 version migration으로만 전진시킨다.
- Boot major upgrade 후 local `bootRun` 검증은 compiler/test와 별개로 필요하다. profile/logging은 application context보다 먼저 실패할 수 있다.
- `.env`를 working directory에 두는 것과 process env에 주입하는 것은 다르다. early initialization 경로는 process env 또는 inline default가 더 안전하다.
- git revision을 archive name에 포함하는 build에서는 `build/libs` 누적 산출물도 runtime 위험이 될 수 있다. IDE classpath가 Gradle classpath와 다르게 움직일 수 있으므로 stale artifact cleanup과 검증을 빌드에 포함해야 한다.
- wiki로 끌어올릴 가치가 있는 일반화된 교훈:
- Major framework migration은 "compile surface", "runtime metadata", "test slice auto-config", "third-party module compatibility", "registry/env contract"를 별도 축으로 검증한다.
- Static-analysis noise cleanup은 "진짜 코드 스타일 위반", "도구 convention", "negative fixture"를 분리해야 한다.
## Related / 관련
- 관련 작업 식별자: `chore-spring-boot-four-migration` (별도 branch-note 없음)
- 선행 patch upgrade 작업 식별자: `chore-spring-boot-patch-upgrade` (별도 branch-note 없음)