From Java 17 and Spring 5 to Java 25 and Spring 6, with 47 critical and high vulnerabilities cut to 0
An independent London wine merchant runs its online store and its back office on one Java codebase built up over more than fifteen years. We moved both onto a supported platform in tested steps, with no rewrite, while the client kept releasing features.
- 47 to 0
- critical and high-severity advisories in the dependency tree: 12 critical and 35 high before the work, none afterOSV scan, before and after
- Java 25
- the current long-term support release, with Spring 6.2, Spring Security 6.5 and Hibernate 6.6Both applications
- 4 weeks
- for the core upgrade, with no rewrite and no feature freezeEngagement
- Client
- An independent London wine merchant: shops, a website, a trade business, and wine held in bond for customers who buy by the case
- Sector
- Ecommerce and retail, with stock, storage billing and fulfilment behind it
- Scope
- An online store and a back-office ERP that share one business core and one database
- Engagement
- Application modernization in verifiable steps; core upgrade in about four weeks
- Stack
- Java 25, Jakarta EE 10, Spring 6.2, Spring Security 6.5, Hibernate ORM 6.6, Tomcat 10.1, PostgreSQL, OpenRewrite, Playwright
- Offer today
- Application Modernization Sprint
A system that still worked, on foundations that no longer got fixes
Nothing was broken. Orders went out, invoices printed and customers checked out every day. The risk sat underneath the application, and it grew every month.
Spring 5.3, Spring Security 5.8 and Hibernate 5.5 had stopped receiving open-source security fixes, and the web layer sat on the javax generation that current libraries are leaving behind. A dependency scan found 12 critical and 35 high-severity advisories, including log4j 1.x and old file upload and search libraries, many with no fix available on the versions in use. The browser code relied on jQuery 1.11 and jQuery UI 1.8, releases from the early 2010s. Several components had been abandoned by their authors and had no version for Spring 6, so they blocked the upgrade path until they were replaced. Automated tests covered little of what the business runs on, which left every change carrying hidden risk.
The system takes card payments and holds customer data. A rewrite was out of proportion to the problem, and staying put was not an option either.
One manageable step at a time
Map and plan
We scanned the full dependency tree to set a baseline, listed every Spring 6 and Hibernate 6 change that would touch this codebase, and agreed an order of work in which each step could be verified on its own.
Build the safety net first
Before any framework moved, we added regression tests around what the business relies on: PDF invoices and documents, spreadsheet imports, product search, orders and billing queries, plus browser tests that walk real journeys through both applications. They earned their place during the upgrade by catching two breaks before release, one that would have stopped all PDF generation and one that would have broken spreadsheet imports.
Upgrade in steps
Each step ended with both applications building, starting and passing the suite. First the vulnerabilities that needed no framework change. Then the platform: Jakarta EE 10, Spring 6.2, Spring Security 6.5, Hibernate 6.6 and Tomcat 10.1, with more than 800 files moved to the new namespaces by automated refactoring and every change reviewed. Then the browser libraries, with the page scripts that used removed functions rewritten. Last, the build and runtime moved to Java 25.
Verify end to end
All 79 automated tests passed and both applications ran on the new server. We then walked the real routes of both applications page by page. Hibernate 6 is stricter than Hibernate 5, so queries that used to pass silently now failed, including one that stalled the product search index at start-up. We found and fixed them before users could meet them.
Hand over
The client's developers received a runbook covering what changed, how to run the new stack and which behaviour differs for users, together with the test suites and the before and after security reports.
Replace what blocks the upgrade, keep what works
The order workflow runs on an old embedded engine that is built too deeply into the business to remove in one phase. Its known vulnerability sat in a single class the application never calls, so we repackaged the engine without that class. Order processing did not change, and the vulnerable code no longer ships. For PDF documents we replaced a library from 2009 with a maintained fork that keeps the same interface, so the invoice and document templates carried on working untouched.
There was no feature freeze. The client's team kept shipping on the main line throughout, including new work on stock and price sync with the shop tills, and we merged each change into the upgrade as it landed.
The stack, by release line
| Component | Before | After |
|---|---|---|
| Java | 17 (2021) | 25 (2025) |
| Enterprise Java API | Java EE 8, javax (2017) | Jakarta EE 10 (2022) |
| Spring Framework | 5.3 (2020) | 6.2 (2024) |
| Spring Security | 5.8 (2022) | 6.5 (2025) |
| Hibernate ORM | 5.5 (2021) | 6.6 (2024) |
| Application server | Tomcat 9 (2018) | Tomcat 10.1 (2022) |
| jQuery | 1.11 (2014) | 3.7 (2023) |
| jQuery UI | 1.8 (2010) | 1.13 (2021) |
| DataTables | 1.10 (2014) | 1.13 (2022) |
| Bootstrap (store) | 3.3 (2014) | 3.4 (2018) |
Years are when each release line first shipped. The patch versions deployed are newer.
Secure today, and easier to keep that way
The upgrade also brought CSRF protection on every form, routes denied by default unless a rule allows them, anti-clickjacking headers, a Content Security Policy running in monitoring mode and integrity checks on every script loaded from a CDN.
- 12 to 0
- critical advisories in shipped dependenciesOSV database, runtime dependencies
- 35 to 0
- high-severity advisories in shipped dependenciesOSV database, runtime dependencies
- 30%
- faster clean builds on the new toolchainBuild times, before and after
- 79
- automated regression and browser tests, all passing on the new stackTest suite handed to the client
What the client received
Upgraded code
Both applications on the new platform, building and running on Java 25.
Security reports
Before and after vulnerability scans, with a repeatable method for running them again.
Test suites
Regression tests for core business functions and browser tests for real user journeys, kept by the client.
Runbook
How to build and run the new stack, and which behaviour changed for staff and customers.
Migration notes
A reference for the Spring 6 and Hibernate 6 changes, so new code avoids the known pitfalls.
Roadmap
A prioritised plan for the next phases of hardening and for the legacy components still to replace.
The offers this engagement proves
Book a scoping call
Thirty minutes with an architect, not a salesperson. You leave with a written view of scope, price model and whether we are the right team for it.