Abstract
In this article, I will present the process of updating Spring Boot in regulatory reporting solutions. I will discuss how we approached this task, how the journey went, and what we learned from it.
We set off (the latest version of Spring Boot makes life easier)
At the beginning, we wanted to use Spring Boot v3 as soon as it was released. Unfortunately, the world is not simple. It poses challenges for us. The first challenge is Java 17. Spring Boot v3 only works on Java 17. At that time, we were still using Java 11. We had no choice but to make a separate preparatory expedition from the island code – named Java 11, to Java 17. I will try to describe this journey in a separate article. It was also quite interesting.
Our setup before updating Spring Boot
Here are a few points that are important from the perspective of migrating to Spring Boot v3:
- We have a monorepo with a dozen or so applications and a lot of code.
- We use Spring Boot and its BOM to manage most of our dependency's versions.
- We want to migrate all applications in one step from this monorepo.
- A requirement from our customers is that one of the applications must be compatible with Java8, in which we use JAXB.
- We use Apache CXF in a few applications to expose SOAP services.
- We obviously use Hibernate very intensively.
- We use XStream.
Preparing for the journey: from the Spring Boot update team
We wanted to perform an update in many applications maintained by many teams. Based on our experience, it is worth organizing such work around a new team appointed for the duration of the update. Each team has nominated a representative who will work on this migration. This allows us to work very efficiently. We have knowledge from each team. Thanks to these representatives, we have short communication channels when solving any encountered problems. This team was formed for the duration of the update. After the key tasks for this migration are completed, it will be dissolved.
We have a team, so we can already set sail
Our Product Owners will not let us go anywhere until we have answered the sacramental question: "How long will it take?"
We were wondering how to answer this question. The first estimate was made using the well-known ancient method: "by the thumb." We assumed that this undertaking should be about the same size for us as our last update from Java11 to Java17. The product Owners are happy.
Sending out scouts
We started with a PoC. We are introducing Spring v3 in our code base and we are trying to compile it at first, and then to launch a few of the most important applications.
The goal of this action is to accurately identify the work that needs to be done.
Here is a list of the main topics that this PoC showed:
- Update dependencies to the highest possible versions that support Spring Boot v2
- Remove redundant dependencies – you know how it is – how many different http clients will be in the code? The answer is simple – exactly as many as there are teams working on these applications. Normalizing to one http client helps a lot.
- Migrate from javax. dependencies to jakarta, Where possible without Spring v3**
- Get rid of deprecated code (e.g., with Spring's @Required annotation, there have been many changes in Spring Security, etc.)
- Prepare a multi-release JAR for a library that must be compatible with Java 8 and intensively uses JAXB.
We are sailing (start moving to Spring Boot 3)
We are trying to work in a Trunk-Based Development mode. However, in this case, we decided that we have to break our own rules and start working in a separate branch. The plan is to update Spring within one sprint. If everything goes well, we will merge the changes in the next sprint.
The sea was rough, and we encountered many problems. Here is a list of the most important ones:
Hibernate
- Hibernate started generating queries for Many To Many relationships in a different way. The queries started to contain "Nested joins", which caused queries to stop executing on one of the databases we use. Issue: https://hibernate.atlassian.net/browse/HHH-16595
We couldn't wait for a fix, so we prepared and applied a workaround (the code was submitted to the Hibernate Jira task – if you are affected by this issue, it might be interesting for you to check it out!)
- The Hibernate expression parser became less forgiving. We had queries in which the navigation to the key was missing when defining the IN expression for searching by ID.
select e from Entity e where e IN (:ids);
but it should be:
select f from Entity e where e.id IN (:ids)
- Again, the problem with the parser became stricter. This time, the problem concerned navigation to properties that are in the subclass.
Select a. value from Abstract Entity
where the property value was introduced only in the subclass of Abstract Entity.
The solution here was to use the appropriate type in the from expression.
- Query generation for get ById... In one application, we had tables that contained cyclic references. In one situation, Hibernate started generating a query consisting of 108 JOINs, where in the previous version with a different tree traversal algorithm, it was about 10 JOINs.
The solution in this case was to give up eager fetch of some properties and fetch some of these relationships using FetchMode = SUBSELECT.
Apache CXF:
- It turned out that this library does not work well with JAXB v4. We were eventually forced to downgrade to version v3. This combination seems to have worked despite the fact that Spring in the BOM has version 4.0.
XStream
- XStream contains a set of cool extensions that handle Hibernate collections. Warning! It is not yet ready for Hibernate 6. We submitted a PullRequest to GitHub: https://github.com/x-stream/xstream/pull/346
Of course, there were also more interesting situations, but I think this list contains the most important ones.
We have implemented the latest Spring Framework version. What did we learn on this journey?
- Keep JDK up to date.
- Keep libraries up to date. Life will be easier.
- Take care of your code base by introducing normalization (you don't need five different HTTP clients).
- Get rid of deprecated code.
- Test, test, test, so that it will be easier with such changes.
- And probably one of the main conclusions: less Hibernate in the future. In many situations, Hibernate was our number one when it comes to choosing a technological stack. This solution definitely has a lot of advantages, but as is often the case in such situations, it also has its disadvantages. If the structure of the objects to be stored is relatively simple, it may be worth thinking three times whether a simple JDBC Template would not be easier, simpler and more convenient to maintain. Why such a conclusion? It seems that in many places, its incorrect use can create more work for you. It is a bit more difficult to make such a mistake with ordinary bare JDBC, because the complexity is immediately visible.