Migrating to Java 17

At SwissBorg, we have been using Scala as our main language from the very beginning. We are from Lausanne after all, where Scala was invented, and home of the Scala Center for which we are members. We like the blend of a strongly typed language and a mature ecosystem, making refactoring more straightforward while giving access to robust libraries from the Java ecosystem. On top of that, libraries like Akka make it a language of choice to build CQRS architectures.

Anyway, we had been running Java 11 for years and the time was come to move to a new LTS version. The impressive performance promised by Java 17, especially regarding garbage collection, was big enough a carrot to push us to migrate. The cherry on top: our Scala 2.13.7 is compatible with Java 17!

Garbage Collection improvements

Java 17 was released in September 2021 and showcased performance improvements, particularly when it comes to garbage collection. This is not the kind of topic most developers tackle regularly and this gave us a good opportunity to look under the hood and look back at some decisions that had been taken in the past few years.

So far, we hadn’t been forcing any GC, effectively letting the JVM choose at startup between the Parallel Collector or the Garbage-first (G1) depending on the context — ie. the heap size and CPU cores. Java 17 provides an updated G1 with preemptive garbage collection (see JDK-8257774 and JEP-346) and the overall performance gap is very significant. To pick a GC, one will look into something that favours latency or throughput according to their needs. Since G1 is well-rounded and we don’t have specific low-latency needs, it seems to still be a good option for us. As a bonus, Java 18 will bring promising improvements for G1 in terms of memory footprint with a reduction of up to 75%!

Memory usage of one of our services, showing the effect of the preemptive G1 GC and how it allows returning unused committed memory (JEP 346)

For more information and comparisons, we recommend you refer to these two great blog posts:

Which JDK to choose?

We were running OpenJDK 11 (from AdoptOpenJDK) and decided to keep it. In the meantime, the organisation rebranded and now goes by the name of Adoptium. The distributed JDKs also got renamed to Temurin — an anagram of “runtime”.

When we started working on this migration, back in November, the Adoptium organisation decided to stop providing JRE, meaning we had to build our using jlink, choose the modules to include, and learn how to use jdeps. Adoptium recently decided to go back on its decision and keep providing official JREs.


The major issue we had while migrating to Java 17 was due to the strong encapsulation of JDK internal. Since Java 16, the --illegal-access flag is set to deny by default, resulting in some calls (mainly related to reflection) being treated as illegal and throwing a beautiful java.lang.reflect.InaccessibleObjectException. In our case, we found out that the libraries Kryo (through Chill) and Aeron use low-level JDK APIs and now trigger illegal access exceptions.

We immediately spotted this issue with our test battery and fixed it by simply allowing access to these internal APIs with a set of extra parameters passed at runtime — --add-opens {package}=ALL-UNNAMED. After a session of trial and error, we came up with the correct list of packages to open. To go further on this topic we recommend looking into JEP 396 and JEP 403.

Regarding our internal libraries, we compile them targeting ClassFile version 55 (Java 11) and completely dropped support for version 52 (Java 8).

We have then slowly rolled out our services with updated containers while monitoring their behaviour closely. It took about two weeks to have them all out.


The amount of work and complexity was worth the amazing performance delta between these two LTS versions. Since we use containers, swapping Java on our base images was fairly straightforward. So easy that we will probably not be so shy experimenting with future non-LTS versions — like Java 18 and its reduction of G1 memory footprint.

A quick reminder that Java 8, unlike Java 11, is at the “end of life” stage and that you should probably update if you haven’t yet. Java 17 has a lot to offer at a price that we found not so expensive, so this might be a good excuse for you to finally bite the bullet too.

Be like SwissBorg, update to Java 17.

Special thanks to Lomig Mégard and Tiago Mota for producing the content of this post and reviewing it 💚




A look into how the SwissBorg Engineering Team enables your crypto investment journey.

Recommended from Medium

🔥 #Roseon is proud to announce that #Swap feature has been released on our app.

APENFT Marketplace starts testnet with an exciting developer sprint

Persistence Layer Library — Boosting DB Performance


Visualize GCP Billing using BigQuery and Data Studio

CI/CD best practices: How to set up your pipeline

DI with Koin, Introduction and Implementation: (Part -1).

How to replicate MySQL Databases?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Julien Deray

Julien Deray

More from Medium

Project Loom — Modern scalable concurrency in Java

Java Reactive Solutions — My impressions on Spring,Quarkus,Jooby and VertX

GraalVM: The future of JVM languages

We Need Java