Upgrading to a major version of Spring Boot was something I’d been wanting to do for a while. I was inspired by an architect named Pacheco, who was my go-to expert on Spring Boot in the previous company, and Alex G in the current company. Recently, I upgraded all 11 services in my company from Spring Boot 2.7 to 3.2. Along the way, I learned a lot — not just about Spring, but also about myself, how our services work, and how to collaborate with my teammates.

One of the toughest parts was not just upgrading the services my platform team owned, but also helping out with other teams’ services to speed up the migration.

In total, I updated 11 services, touching over 1,400 files across more than 50 merge requests (MRs), and nearly rewrote two services from scratch.

I learned a lot, both technically and non-technically, but in this post, I’ll focus on the non-technical lessons. They are not exactly new things, but reminders of important things when doing such changes.

Key Lessons from the Spring Boot Upgrade

keep changes minimal

When I work on code, I usually like to improve things as I go. But during this upgrade, I realized that with such a big change, it’s better to only modify what’s absolutely necessary. Keeping changes minimal made it easier to troubleshoot any issues that popped up. When you make too many changes at once, it’s harder to figure out what caused the problem. So, it’s best to make the required changes first, then handle any improvements in follow-up MRs.

deprecations can be a pain

We had some things, like Netflix Zuul or Auth Resource Server, that were already deprecated in 2.7 but still in use. We managed to remove some before the upgrade, but others had to be dealt with during the process. The best approach is to clear out as many deprecations as possible before upgrading. The fewer deprecated elements you have, the smoother the upgrade will be.

tests are essential

This might be obvious, but when you’re making a big change like a Spring Boot upgrade, tests are crucial. They make it much easier to catch problems. Having multiple layers of testing — like unit tests and end-to-end tests — helps a lot. For instance, some issues, like conflicting library versions, might not show up in unit tests but could be caught in end-to-end tests after building the Docker image.

monitoring is a must

Even with great test coverage and manual testing, production can still throw surprises your way. Good monitoring and SLOs (Service Level Objectives) are essential to ensure everything is working as expected. For example, we quickly noticed a performance drop after the upgrade thanks to our API latency SLOs and were able to fix it by adding indexes to the database.

supporting team

While I did most of the work and created all the MRs, my team was crucial in reviewing my changes, ensuring I was on the right track, and, most importantly, helping out when issues arose. Having someone to debug and find the root cause with you is invaluable.

expect the unexpected

No matter how thoroughly you test, there will always be an edge case that slips through. It’s important to be prepared for this and to understand the changes and their potential impacts as much as possible.

understanding management

Before starting the upgrades, I estimated how long it would take to complete all the services. Although I finished within my overall timeline, some services took longer due to unexpected issues like library conflicts or unmapped use cases. Having understanding management is key because things can get unexpectedly complex.

plan for rollbacks

Even after thorough testing, you can never be 100% sure things will go smoothly. That’s why having a rollback plan is essential. Make your changes as backward-compatible as possible so you can revert them if needed. Even if you don’t end up needing it, having that option provides peace of mind.

communication is key

When making big changes, especially to other teams’ services, good communication is vital. It helps build confidence that everything is being handled properly and allows more eyes on the changes, which can help catch issues faster. It’s also important to keep other teams, like support and customer success, in the loop so they can connect any new issues from clients to the changes made.

technical problems will get solved

No matter how tough a problem seems at the moment, technical issues can be figured out. Sometimes you might not pinpoint the exact line of code causing the problem, but you’ll usually get close enough to find a solution or a workaround. We had a few problems that took hours, and a couple that took days to fully understand, but we eventually figured them out.


I hope these lessons help you when performing a big upgrade in your systems. They’re not limited to Spring Boot and can be applied to any significant change.

I didn’t dive into the technical details here, but if you’re interested, I’ve written some posts about the technical aspects of this upgrade. Here are a few:

Cheers.