Add boot4-migration module: one Boot 3.5.16 service migrated to 4.1.2, with the real compile, start-up, silent and test failures captured at each step

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b4VQVEJWetTT1dYzyJ
This commit is contained in:
2026-10-29 05:57:26 +00:00
co-authored by Claude Sonnet 5.5
parent fd0cf9b527
commit 85074ed51a
45 changed files with 1021 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env bash
# Regenerates every file in output/. Needs JDK 25 (JAVA_HOME), Maven 3.9 and network access to Maven Central.
# The "before" app (Boot 3.5.16) is never built in place: each stage is copied to a scratch directory.
# Takes 15-25 minutes on a shared 2-vCPU box (each stage is a real Maven build and most start the app).
set -u
HERE=$(cd "$(dirname "$0")/.." && pwd); OUT=$HERE/output; W=${WORK:-/tmp/b4m-work}
MVN=${MVN:-mvn}; PORT=${PORT:-18090}; URL=http://localhost:$PORT
BOOT_OLD=3.5.16; BOOT_NEW=4.1.2
mkdir -p "$OUT"; rm -rf "$W"; mkdir -p "$W"
mv_() { local d=$1; shift; (cd "$d" && $MVN -B -T1 "$@" 2>&1 | grep -vE 'JAVA_TOOL_OPTIONS|^WARNING: |Download(ing|ed)' | sed "s#$W/##g"); }
stage() { rm -rf "$W/$1"; mkdir -p "$W/$1"; cp -r "$HERE/$2/pom.xml" "$HERE/$2/src" "$W/$1/"; }
bump() { sed -i "s#<version>$BOOT_OLD</version>#<version>$BOOT_NEW</version>#" "$W/$1/pom.xml"; }
AFTER_MAIN=$HERE/after/src/main/java/com/ankurm/migration
BEFORE_TEST=$HERE/before/src/test/java/com/ankurm/migration
# start <label> <jar> [extra args]; leaves the app running on $PORT, pid in $W/pid
start() {
local label=$1 jar=$2; shift 2
rm -rf "$W/logs-$label"
LOG_DIR="$W/logs-$label" java -jar "$jar" --server.port=$PORT --spring.security.user.password=pw "$@" > "$W/run-$label.log" 2>&1 &
echo $! > "$W/pid"
for _ in $(seq 1 240); do curl -s -o /dev/null $URL/actuator/health && return 0; kill -0 "$(cat "$W/pid")" 2>/dev/null || return 1; sleep 1; done
return 1
}
stop() { kill "$(cat "$W/pid")" 2>/dev/null; wait "$(cat "$W/pid")" 2>/dev/null; sleep 1; }
g() { echo "GET $1"; curl -s -u user:pw -w ' -> HTTP %{http_code}\n' "$URL$1" | cut -c1-200; }
# probe <label> <jar> [extra args] -> prints what the running app does
probe() {
local label=$1; shift
if ! start "$label" "$@"; then
echo "APPLICATION DID NOT START. The failure report:"; sed -n '/APPLICATION FAILED TO START/,/^Action:/p' "$W/run-$label.log" | grep -v '^$' | head -12
stop; return
fi
g /summary; g /greetings/1; g /greetings/count; g /aspect; g /gateway
echo "GET /actuator/metrics (first 60 chars)"; curl -s -u user:pw -w ' -> HTTP %{http_code}\n' $URL/actuator/metrics | cut -c1-60
echo "GET /actuator/health (groups and status only)"
curl -s $URL/actuator/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print(" groups=%s status=%s components=%s" % (d.get("groups"), d["status"], sorted(d.get("components",{}))))'
echo "GET /actuator/health/liveness"; curl -s -o /dev/null -w ' -> HTTP %{http_code}\n' $URL/actuator/health/liveness
if curl -s -u user:pw $URL/greetings/count | grep -q '"status":500'; then echo "root cause of that 500 in the application log:"; grep -oE 'Table "[A-Z]+" not found' "$W/run-$label.log" | sort -u | sed 's/^/ /'; echo "Flyway log lines at start-up: $(grep -c 'org.flywaydb' "$W/run-$label.log")"; fi
for _ in $(seq 1 40); do curl -s $URL/greetings/1 > /dev/null; done
echo "after 40 more requests, log directory holds: $(ls "$W/logs-$label" | sed 's/\.[0-9-]*\.[0-9]*\.gz/.<date>.<n>.gz/' | sort | uniq -c | tr -s ' ' | tr '\n' ';') (app.log is $(stat -c %s "$W/logs-$label/app.log") bytes; limit set is logging.file.max-size=2KB or its 4.1 replacement)"
stop
}
hdr() { echo "\$ $*"; }
# ---------------------------------------------------------------- 01 baseline on Boot 3.5.16
stage s0 before
{ hdr "mvn package # before/ as written: Boot $BOOT_OLD, JDK $(java -version 2>&1 | grep -v JAVA_TOOL | head -1 | cut -d'"' -f2)"
mv_ "$W/s0" package > "$W/s0.log"; grep -E 'Tests run:|CSRFPROBE|BUILD' "$W/s0.log" | grep -v 'Time elapsed'
echo; hdr "java -jar migration-demo-1.0.0.jar # Boot $BOOT_OLD"
probe boot35 "$W/s0/target/migration-demo-1.0.0.jar"; } > "$OUT/01-before-boot35.txt"
cp "$OUT/01-before-boot35.txt" "$W/probe-boot35.txt"
# ---------------------------------------------------------------- 02 change nothing but the parent version
stage s1 before; bump s1
{ hdr "mvn -DskipTests package # same sources, same pom, only <parent><version> $BOOT_OLD -> $BOOT_NEW"
mv_ "$W/s1" -DskipTests package | grep -E 'ERROR.*(dependency|missing)|BUILD' | sort -u
echo; echo "# spring-boot-starter-aop has no managed version any more. Rename it to spring-boot-starter-aspectj and try again:"
sed -i 's/spring-boot-starter-aop/spring-boot-starter-aspectj/' "$W/s1/pom.xml"
hdr "mvn compile"
mv_ "$W/s1" compile > "$W/s1.log"
grep -E 'BUILD|Fatal error compiling' "$W/s1.log" | sed 's/ -> \[Help 1\]//' | sort -u | cut -c1-230
echo "# Maven's compiler plugin 3.15.0 on JDK 25 crashed inside javac instead of listing the errors, so ask javac directly:"
hdr "javac -cp \$(mvn dependency:build-classpath) src/main/java/**/*.java"
(cd "$W/s1" && $MVN -B -q dependency:build-classpath -Dmdep.outputFile="$W/cp.txt" > /dev/null 2>&1; mkdir -p "$W/jc"
javac -d "$W/jc" -cp "$(cat "$W/cp.txt")" $(find src/main -name '*.java') 2>&1 | grep -v JAVA_TOOL > "$W/s1.javac")
grep -A1 -E 'error:' "$W/s1.javac" | grep -v '^--' | sed 's#^src/main/java/com/ankurm/migration/##'
echo "javac error count: $(grep -c 'error:' "$W/s1.javac")"; } > "$OUT/02-bump-only-compile-errors.txt"
# ---------------------------------------------------------------- 03 imports fixed, starters untouched -> startup failure
stage s2 before; bump s2; sed -i 's/spring-boot-starter-aop/spring-boot-starter-aspectj/' "$W/s2/pom.xml"
rm -rf "$W/s2/src/test"; cp "$AFTER_MAIN"/JsonConfig.java "$AFTER_MAIN"/SecurityConfig.java "$W/s2/src/main/java/com/ankurm/migration/"
{ hdr "# s2 = before/ + Boot $BOOT_NEW + aop->aspectj + JsonConfig and SecurityConfig ported (imports only); every other dependency untouched"
hdr "mvn -DskipTests package"
mv_ "$W/s2" -DskipTests package | grep -E 'ERROR|BUILD'
echo; hdr "java -jar migration-demo-1.0.0.jar"
probe starters-missing "$W/s2/target/migration-demo-1.0.0.jar"; } > "$OUT/03-compiles-but-will-not-start.txt"
# ---------------------------------------------------------------- 04 start-up fixed -> the silent ones
stage s3 before; bump s3; sed -i 's/spring-boot-starter-aop/spring-boot-starter-aspectj/' "$W/s3/pom.xml"
rm -rf "$W/s3/src/test"; cp "$AFTER_MAIN"/JsonConfig.java "$AFTER_MAIN"/SecurityConfig.java "$W/s3/src/main/java/com/ankurm/migration/"
sed -i 's#<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc#<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-restclient</artifactId></dependency>\n <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc#' "$W/s3/pom.xml"
{ hdr "# s3 = s2 + spring-boot-starter-restclient (the only thing the start-up failure asked for). Flyway, properties and the rest still Boot 3 style."
hdr "mvn -DskipTests package"
mv_ "$W/s3" -DskipTests package | grep -E 'ERROR|BUILD'
echo; hdr "java -jar migration-demo-1.0.0.jar"
probe silent "$W/s3/target/migration-demo-1.0.0.jar"; } > "$OUT/04-starts-but-silently-wrong.txt"
cp "$OUT/04-starts-but-silently-wrong.txt" "$W/probe-silent.txt"
# ---------------------------------------------------------------- 05 properties migrator on the silent build
stage s6 before; bump s6; sed -i 's/spring-boot-starter-aop/spring-boot-starter-aspectj/' "$W/s6/pom.xml"
rm -rf "$W/s6/src/test"; cp "$AFTER_MAIN"/JsonConfig.java "$AFTER_MAIN"/SecurityConfig.java "$W/s6/src/main/java/com/ankurm/migration/"
sed -i 's#<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc#<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-restclient</artifactId></dependency>\n <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-properties-migrator</artifactId><scope>runtime</scope></dependency>\n <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc#' "$W/s6/pom.xml"
{ hdr "# s6 = s3 + spring-boot-properties-migrator (runtime scope)"
mv_ "$W/s6" -DskipTests package | grep -E 'ERROR|BUILD'
start migrator "$W/s6/target/migration-demo-1.0.0.jar" && echo "app started" || echo "app did not start"
hdr "grep -iE 'propert|logging.file|migrat' run.log # what the migrator printed at start-up"
grep -iE 'logging\.file|PropertiesMigration|The use of configuration keys|Property source|Reason|Replacement' "$W/run-migrator.log" | sed 's/^[0-9T:.+-]* *//' | cut -c1-220 | head -20
echo "(lines matching: $(grep -ciE 'logging\.file|PropertiesMigration' "$W/run-migrator.log"))"
stop; } > "$OUT/05-properties-migrator.txt"
# ---------------------------------------------------------------- 06 the migrated app
{ hdr "mvn verify # after/ : Boot $BOOT_NEW, one starter per technology, -test companions, Jackson 3, new packages"
stage a after
mv_ "$W/a" verify > "$W/a.log"; grep -E 'Tests run:|CSRFPROBE|BUILD' "$W/a.log" | grep -v 'Time elapsed'
echo; hdr "java -jar migration-demo-1.0.0.jar"
probe after "$W/a/target/migration-demo-1.0.0.jar"; } > "$OUT/06-after-boot41.txt"
cp "$OUT/06-after-boot41.txt" "$W/probe-after.txt"
{ hdr "java -jar migration-demo-1.0.0.jar --spring.jackson.use-jackson2-defaults=true"
start j2 "$W/a/target/migration-demo-1.0.0.jar" --spring.jackson.use-jackson2-defaults=true && g /summary; stop; } > "$W/probe-j2.txt"
# ---------------------------------------------------------------- 07 Jackson order, side by side
{ echo "# GET /summary on the same bean (id, title, createdAt, author declared in that order)"
echo "Boot $BOOT_OLD (Jackson 2): $(grep -A1 '^GET /summary' "$W/probe-boot35.txt" | tail -1)"
echo "Boot $BOOT_NEW (Jackson 3, defaults): $(grep -A1 '^GET /summary' "$W/probe-after.txt" | tail -1)"
echo "Boot $BOOT_NEW + spring.jackson.use-jackson2-defaults: $(grep -A1 '^GET /summary' "$W/probe-j2.txt" | tail -1)"; } > "$OUT/07-jackson-property-order.txt"
# ---------------------------------------------------------------- 08 test code
stage t1 after; rm -rf "$W/t1/src/test"; mkdir -p "$W/t1/src/test/java/com/ankurm/migration"
cp "$BEFORE_TEST"/GreetingControllerTest.java "$W/t1/src/test/java/com/ankurm/migration/"
{ hdr "mvn test-compile # after/ pom and main code, but the Boot 3 GreetingControllerTest (@MockBean, old @AutoConfigureMockMvc package)"
mv_ "$W/t1" test-compile > "$W/t1.log"; grep -E '^\[ERROR\] .*\.java' "$W/t1.log" | sed 's#^\[ERROR\] ##' | sort -u; grep -E 'BUILD' "$W/t1.log"; } > "$OUT/08-test-compile-errors.txt"
stage t2 after; rm -rf "$W/t2/src/test"; mkdir -p "$W/t2/src/test/java/com/ankurm/migration"
cp "$BEFORE_TEST"/MockFieldTest.java "$W/t2/src/test/java/com/ankurm/migration/"
sed 's#org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc#org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc#' "$BEFORE_TEST"/CsrfProbeTest.java > "$W/t2/src/test/java/com/ankurm/migration/CsrfProbeTest.java"
{ hdr "mvn test # after/ pom and main code + the Boot 3 MockFieldTest unchanged (compiles) and CsrfProbeTest (one import fixed)"
mv_ "$W/t2" test > "$W/t2.log"; grep -E '^\[(ERROR|INFO)\] Tests run:|CSRFPROBE|^\[ERROR\] MockFieldTest|BUILD' "$W/t2.log" | grep -v 'Time elapsed'; } > "$OUT/09-test-runtime-failure.txt"
bash "$HERE/scripts/security-dsl.sh" > /dev/null
echo "done"; ls -l "$OUT"