Skip to content
SHEET ARCHITECTURE · CASE STUDY

Modernizing a Micronaut 1 Monolith Without a Big Bang

Micronaut 1 to 4, Java 11 to 17, Gradle 7 to 8, and Hibernate 5 to 6 across a monolith with no tests, then a far cheaper second jump to Java 25

ARCHITECTURE SR. SOFTWARE ENGINEERSTARTUP
1 → 5
MICRONAUT MAJOR VERSIONS, TWO PLANNED JUMPS

The Problem

The core production system is a Java and Groovy monolith with 13,047 commits and more than 30 Gradle modules, originally built by a contracting firm and inherited by an internal team that has never been larger than nine engineers at any one time (48 people have committed to the repository over its life). When I picked up the modernization, it was pinned to Micronaut 1, Java 11, Gradle 7, and Hibernate 5: a toolchain years out of date, with framework support drying up and every current library release drifting further out of reach. It also had no test suite. Not unit tests, not integration tests, nothing to say whether an upgrade had broken something except running the application and watching.

The upgrades were interlocked. Micronaut 4 wanted a newer Java and a newer Gradle; Hibernate 6 reworked type handling underneath the data layer; none of them could move alone. And the business could not stop shipping while we caught up, so a rewrite was off the table and so was a freeze.

The Approach

The modernization was one program told in two jumps, and the first one was the real fight.

FIG. 17 / MODERNIZATION · FRAMEWORK & RUNTIME EVOLUTION (MICRONAUT 1 TO 5)
PRODUCTION MONOLITH · MICRONAUT 5 · JAVA 25 · SHEET MOD-01
~1 YEAR EFFORT EVERY FILE TOUCHED COORDINATED BUMP 9 MODULES TO GREEN MICRONAUT 1 JAVA 11 · GRADLE 7 HIBERNATE 5 · NO TEST SUITE 13,047 COMMITS · 30+ MODULES MICRONAUT 4 JAVA 17 · GRADLE 8 HIBERNATE 6 · SPOCK TESTS JIB · CI 38 TO 11 MIN MICRONAUT 5 JAVA 25 · GRADLE 9.5 HIBERNATE 7 · GROOVY 5 SAME-DAY GREEN · 1 MONTH SETTLE

Micronaut 1 to 4 took the better part of a year and touched every file in the repository. Java 11 to 17, Gradle 7 to 8, and Hibernate 5 to 6 moved with it, because each one pinned the others. There was no big-bang branch: modules were walked to green individually and merged continuously into the same trunk the rest of the team was shipping against, so the migration and the roadmap shared one codebase the whole way.

The build system had to be fixed before the migration could move. The CI pipeline rebuilt every application on every change and took roughly 38 minutes to do it, which made an every-file migration unworkable. Replacing the Docker builds with JIB and adding per-application change detection, after a move to self-hosted runners, took the median to about 11 minutes, and that work became its own case study.

Tests were built as the forcing function, not retrofitted afterward. Every class the upgrade touched got comprehensive Spock integration tests before that work counted as done, which is how a codebase with zero tests came out of the migration with a real suite. Partway through I rebuilt my own workflow around AI-assisted development, a shift covered in its own article; in the final push the team created and closed over 50 Jira tickets in two weeks. The upgrade shipped in January 2026.

The follow-up jump proved the point of the first one. A couple of months later I took the same monolith from Micronaut 4 to 5, Java 17 to 25, Gradle 8 to 9.5, and Hibernate 6 to 7, with Groovy 5 riding along, in a single coordinated bump. It was a far smaller job: nine modules walked back to green the same day per the commit records, followed by roughly a month of regression cleanup. The nastiest regression was a Jackson double-decode bug that silently broke SAML login (it does not throw; it just authenticates nobody), and the noisiest was Micronaut 5 changing JUnit’s default test order, which surfaced tests that had always depended on running in sequence and had to be separated from the real breaks before the suite could be trusted again.

Results

The monolith is current on every axis: Micronaut 5 on Java 25, Gradle 9.5, Hibernate 7, Groovy 5. The honest measure of the program is the cost curve between the two jumps. The first took the better part of a year and touched every file; the second took days to walk back to green and a month to fully settle, because the first jump left behind the things that make staying current cheap: a real test suite, a fast build, and a module-by-module discipline that never needed a freeze. I was the sole driver of the modernization program end to end; across the repository’s 48 lifetime contributors, my combined identities rank #3 of 46 by no-merge commits. The same-day green walk is what the commit records show, not suites I re-ran independently, and I cannot trace from git history whether the transition window produced any customer-facing incident elsewhere in the monolith, so I am claiming an untraced record rather than a clean one. Staying current is a muscle, not a milestone.