[/su_list] …which you may find useful if you're looking to embark on a similar journey yourself, whether you use IPF or not.
Well, besides the normal security and supportability reasons for our customers, the main reason a Java upgrade came onto our radar was this announcement in 2022 from Juergen Hoeller stating that Spring 6+ and Spring Boot 3+ will only be supporting Java 17+. Also, there were increasing grumblings and growing dissent among the ranks as they were not able to use some of the cool new features in Java 12→17, such as records, sealed types, switch expressions (to name but a few). So, not wanting to be ousted in a mutiny, I proposed that we should allow some time to target an IPF release, do this upgrade, and communicate the change to customers.
In addition, there have been generational garbage collection (GC) improvements between Java 8 and 17, even if just using the default GC configuration. This blog post by Stefan Johnasson from the JDK GC team at Oracle describes the GC improvements the GC team has managed to achieve for the JDK between Java 8 and Java 17.
You read that right. We moved from Java 11 to 17, and not to 21. The main reason was that it was a lowest common denominator: we surveyed all of our customers around October/November last year (2023), and all of them replied that they had support for Java 17 (released ~2 years before the survey), but only one or two declared support for Java 21 (released ~2 months before the survey).
Another reason is that we wouldn't have benefited from arguably the biggest change to Java 21: virtual threads. This is because Akka - the non-blocking, message-oriented, async framework we use for event sourcing, clustering, and a bunch of other things - only became certified for Java 21 as part of their 24.05 release, which was released on 17 May 2024. We wouldn't be independently writing virtual thread code as part of this upgrade, so Java 17 allowed us to be compatible with Spring 6/Spring Boot 3, and helped me avoid walking the plank.
Frankly, the world of payments processing is too important to risk, and there are no prizes for introducing the risk of being an early adopter. Let someone else (other domains and use cases) iron out the kinks!
Here be dragons! I'm getting into the weeds a bit here. If you're not interested in this stuff then you can skip this section.
So, what have I got to do? We use Maven for our build and dependency management, so surely I can just change some magical permutation of properties and plugin configurations according to this mini novella on StackOverflow to change the Java version from 11 to 17. OK, that's done, and...
[ERROR] Failed to execute goal [org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.14.0:generate (default) on project shared-domain: Execution default of goal org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.14.0:generate failed: An API incompatibility was encountered while executing org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.14.0:generate: java.lang.ExceptionInInitializerError: null
What a delightfully helpful and informative error message to start me off on my journey. The maven-jaxb2-plugin has exploded into smithereens.
Because we are ISO20022-based, and most of the payment schemes we support are also ISO20022-based, we have to deal with a lot of XML This means that we have quite a few JAXB Maven plugin executions that take in an XML schema and generate value objects. Digging into the exception, we get a bunch of nonsense I will not reproduce here, but the root cause was #1 of three horsemen of the apocalypse I would continue to encounter for about a month:
| Horseman | Error message | What it means |
| 1 | java.lang.NoSuchMethodError: [some class or method] |
I expected this class or method but it's not here any more (or its signature has changed) |
| 2 | java.lang.reflect.InaccessibleObjectException: Unable to make [a] accessible: module [b] does not "opens [c]" to [d] |
Moving to Java 17 is fun |
| 3 | (anything else) | Mystery fun time! |
So I just had to upgrade this and many other plugins and dependencies. As you can imagine though, defeating one horseman from the above table simply summoned another. So I had to keep going around and consulting many GitHub issues, StackOverflow pages, forum posts and so on, until I was able to achieve a set of dependencies and BOMs that could hang together without causing a compile-, build-, or runtime explosion.
javax.* with jakarta.* - this sometimes blew up in my face, but I want to say it was a net positive?We actually wrote a guide for our customers on upgrading to Java 17/Spring 6/Spring Boot 3 containing all issues that I came across which they too might come across in their IPF implementations. I won't reproduce it here because this post is already dangerously long, but you can click here to view our migration docs that contain this advice.
I am aware of the existence of OpenRewrite - but honestly, most of the challenges I'd faced were not ones that this was designed to address (not easily, anyway), and I decided that it was causing more issues than it was solving (adding weird dependencies because of it not being aware of dependencyManagement of a parent project, putting files in strange places). I don't think our projects are niche or unusual, but it was taking more time to understand OpenRewrite's strange behaviour than to write the alias mentioned above.