Upgrade to Java 21 and Spring Boot 3.5.16, with tests, CI and Java 21 modernization - #69
devin-ai-integration[bot] wants to merge 12 commits into
Conversation
- pom: spring-boot-starter-parent 2.0.2.RELEASE -> 3.5.16, java.version 1.8 -> 21 - pom: packaging pom -> jar so an executable jar is actually produced - pom: drop spring-boot-properties-migrator (a 1.x -> 2.0 aid), add spring-boot-starter-test - maven wrapper 3.3.9 -> 3.9.9 - remove the unmaintained Gradle build (Gradle 4.6 cannot run on JDK 21) - bug: tolerate the unreachable demo quote service instead of failing startup - move the RestTemplate bean out of Application to avoid a self-injection cycle - replace the deprecated JdbcTemplate.query(String, Object[], RowMapper) overload - stop tracking target/ and .idea/ (both already declared in .gitignore) Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
…drop stray root properties file Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
…ontroller - convert Topic, Customer, Greeting, Quote, Value to records (component names preserve the existing JSON field names) - add @FunctionalInterface to CustomPredicate - use .toList() where the result is only read; leave the mutable topics list alone - bug: getTopicWithId returns Optional so an unknown id yields 404 instead of 500 Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
…r Java 21 Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
… TopicService and TimeClient Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
Runtime verification — Java 21 / Spring Boot 3.5.16 upgradeBlack-box tested the built jar on The crux — the app starts and stays up. The query returning exactly Bloch + Long confirms the Endpoints verified (click to collapse)
Full CRUD lifecycle — POST added a 4th topic, PUT updated in place (count stayed 4), DELETE restored the original 3:
Also passed: Adversarial casesTwo expected deltas (neither is a bug in this PR)
Verdict: behaviourally equivalent to the Java 8 app on every endpoint, with the intended change being the quote fetch going from fatal to log-and-continue. Caveat worth stating plainly: |
The test suite was written in parallel against the pre-modernization API: - Topic accessors are record components now (getId -> id) - getTopicWithId returns Optional and an unknown id is a 404, not a 500, so the placeholder assertions and their @disabled twins collapse into one - the RestTemplate bean is a RestClient bean Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
The runner cannot resolve actions/checkout or actions/setup-java for this repository (Service Unavailable at 'Getting action download info'), so the job failed before running anything. Checkout via git and use the JDK 21 already present on the runner image. Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
Summary
Brings this app from Spring Boot 2.0.2 / Java 8 to Spring Boot 3.5.16 / Java 21, and — because the repo exists to demonstrate language features — updates what it demonstrates to match the runtime it now runs on.
The upgrade itself was nearly free: the codebase contains no
javax.*imports anywhere (no JPA, no Bean Validation, no servlet API, no Security), so the jakarta namespace migration that makes most Boot 2→3 upgrades painful simply doesn't apply here. Changing four lines ofpom.xmlcompiles clean. Everything else in this PR is the work that the upgrade exposed.Boot 3.5.16, not 4.x, deliberately: Boot 4 is a separate migration (Spring Framework 7, module restructuring,
RestTemplateremoval) and bundling it with a JDK jump would make any breakage ambiguous.The app did not start before this PR — twice over
CommandLineRunnerfetched a quote fromgturnquist-quoters.cfapps.io, a demo service that no longer exists, and let theResourceAccessExceptionescape, so the process exited after the web server came up. The fetch was also duplicated acrossmain()and a second runner bean. Now: one call, log-and-continue.Applicationdeclared theRestTemplate@Beanand@Autowiredit into itself. Boot 2 tolerated this; Boot 3 rejects it withUnsatisfiedDependencyException: Requested bean is currently in creation. The bean moved out to its own@Configurationclass.Neither is caused by the upgrade, but both had to be fixed for "it runs" to mean anything.
Build
pom.xmlpackagingpom→jar. It waspom, so the build never produced a runnable artifact.spring-boot-properties-migrator(a 2.x migration aid).compile/testCompilewere removed in Gradle 7, andbootJar { baseName }was removed in Boot 3. A build file that cannot build is worse than no build file../mvnw clean verifyon JDK 21. The repo has ~70 abandoneddevin/*upgrade branches and no CI; nothing ever forced a branch to be provably green.target/and.idea/were committed despite a.gitignore; they're now untracked (that's the ~2.6k deleted lines).Tests — the repo had none
41 tests, from zero: full-context startup, MockMvc coverage of every endpoint,
TopicServicestream/regex/IntStream units, andTimeClientdefault/static interface methods.TopicServicekeeps mutable state on a singleton bean, so tests get a fresh instance rather than sharing one, and the file-walking endpoints assert loosely because they depend on the process working directory.Java 21 modernization
Quote/Valueare Jackson-deserialized and previously relied on setters; Jackson handles records natively.GET /topic/{unknown-id}returns 404 instead of 500. It was.findFirst().get()→NoSuchElementException. Service returnsOptional, controller maps the miss:spring.threads.virtual.enabled=true), verified as actually taking effect rather than assumed.RestTemplate→RestClient(RestTemplate is in maintenance mode), keeping the log-and-continue behaviour.+-concatenated response building inHelloController, with\continuations so the single-line response bodies stay byte-identical — diffed against live pre-changecurloutput.Collectors.toList()→.toList()only where the result isn't mutated —TopicService.topicsis added to and removed from, and.toList()is unmodifiable, so a blanket conversion fails at runtime rather than at compile time.application.propertiesat the repo root containing the literal linepublic; it was never on the classpath.Verification
./mvnw -B clean verify— BUILD SUCCESS, 41 tests, 0 failures, 0 skipped, on JDK 21./,/topic(GET/POST/PUT/DELETE),/topic/{id},/topic/sort,/topic/minimum/length/{n},/datetime,/topic/string/operation,/topic/file/operation.query(sql, Object[], RowMapper)→query(sql, RowMapper, Object...)migration, since a mis-bound?returns 0 or 4 rows.Known deltas, called out rather than buried
/topic/file/operation's twograd*file searches now return empty strings.TopicServicescans the working directory for files starting withgrad, and this PR deletes the Gradle build. The endpoint still returns 200; two of its four demo outputs are permanently blank. Intended consequence of going Maven-only.Customer.toString()output changes format (hand-written → record-generated). It only appears in startup logs.PUT/DELETEon an unknown topic id are still silent 200 no-ops. Out of scope here; the 404 work was scoped toGET.Composition
Built as five branches merged into this one: the base upgrade, then tests+CI, models→records+404, presentation+README, and runtime config, developed in parallel and reconciled here — the test suite was written against the pre-modernization API, so its
Topicaccessors, 404 placeholders andRestTemplatebean assertion were updated in the final commit.Link to Devin session: https://app.devin.ai/sessions/779507d78c0b4947b173a22b8b534c1b
Devin Review