DEV Community

Cover image for My feedback after upgrading a Quarkus project from v2 to v3
Yassine Benabbas for Technology at Worldline

Posted on • Edited on • Originally published at blog.worldline.tech

My feedback after upgrading a Quarkus project from v2 to v3

In our team (Worldline TechSquad), we are develop and maintain a CRUD REST API for managing our internal events. The development started with Kotlin and Quarkus 1 which was updated to Quarkus 2.
Since July 2023 Quarkus 3.2 is the current LTS release. Thus, we wanted to update our app as soon as possible to remain on a proper long term release.

Easy upgrade

This post shares my experience upgrading our REST API from Quarkus 2 to Quarkus 3 hoping that it'll help you avoid our issues if you do a similar upgrade 😊.

(Almost) Easy upgrade with Quarkus-cli

The official documentation recommends to upgrade using the quarkus-cli by running quarkus update. However, this did not work on our project on the first try.
This may be caused by many reasons, maybe because our project was not created using a recent Quarkus starter or because we modified the POM file a bit too much.
For example, we were not using the Quarkus BOM and we were specifying the version on each Quarkus library.

Since my assumptions were based on a POM file which is not quarkus-cli friendly, I decided to make a new one.
In order to do that, I first created a new project through code.quarkus.io and selected all the extensions that we use in our project: Kotlin, Postgres and Panache, RESTEasy Classic Jackson, etc. After that, I replaced our old POM with the one the generated one. Finally, I added the libraries that we use and that were not available as extensions, such as arrow.

After updating the POM and checking that the project still compiles, I tried to run quarkus update again but the update failed. Since I'm on Windows I tried on PowerShell Core and on CMD but both failed. My last hope was using a proper Linux environment: WSL.

So I installed the quakus-cli, again, on a WSL Ubuntu and ran another quarkus update.
This time, the upgrade successfully completed. One of the biggest changes that this tool did was to rename all javax imports to jakarta.
I didn't count how many lines were affected but I think it was near to 100 lines.
So the upgrade tool proved to be really helpful.

After the upgrade, our project was also building without issues and the REST API was running again. Yay!
The next step was to check if there were no regressions on runtime.

Issues encountered after the upgrade

Can you guess if we encountered any issues ? Of course yes!

Actually, most of the bugs that we had originated from migrating Hibernate from version 5 (used by Quarkus 2) to version 6 (used by Quarkus 3). Quarkus provided two migration guides (this one, and this other one) just for Hibernate. Since I didn't read the docs beforehand 🤦‍♂️, the issues that we encountered were to be expected.

Here is a listing of the errors that we got and how we fixed them:

Composite keys new requirement

Composite keys require to implement Comparable or they fail to instantiate at runtime.

// before: fails on Quarkus 3 (with Hibernate 6)
data class CompositeKey(
  var reference: String = "", 
  var session: OtherCompositeKey = OtherCompositeKey()
  ) : Serializable
Enter fullscreen mode Exit fullscreen mode

To fix this, we made our composite keys implement Comparable as follows:

// after
data class CompositeKey(
  var reference: String = "",
  var otherCompositeKey: OtherCompositeKey = OtherCompositeKey()
  ) : Serializable, Comparable<CompositeKey> {
    // Comparing reference works in our case becase we use a UUID 
    override fun compareTo(other: CompositeKey) 
      = reference.compareTo(other.reference)
}
Enter fullscreen mode Exit fullscreen mode

Our implementation of compareTo(other: CompositeKey) compares only reference field instead of using both fields of the composite key. This works fine for us (at least for now) because we use UUIDs for references, and it's very rare to get a duplicate UUID. Maybe in the future we'll implement a more correct compareTo like this one :

override fun compareTo(other: CompositeKey) 
  = (reference + otherCompositeKey)
       .compareTo(other.reference + other.otherCompositeKey)
Enter fullscreen mode Exit fullscreen mode

What do you think ?

Implicit foreign column names not working anymore

We had JPQL queries that reference foreign columns and we used the names generated by hibernate. After migrating to Quarkus 3 (thus Hibernate 6), those queries failed because the foreign columns were not found anymore. For example, these foreign column names were not detected anymore in JPQL: edition_event_reference and edition_reference.

It looks like we could have fixed this using the foreignKey attribute of the @JoinColumn annotation as explained in this post.

@ManyToOne
@JoinColumn(name = "edition_id", foreignKey = @ForeignKey(name="edition_reference"))
private Edition editionReference;
Enter fullscreen mode Exit fullscreen mode

But in our case, we simply replaced those columns with join statements. For example, we replaced uses of edition_reference by a join; select s from Session s join s.edition e.

It's good to be lazy

Non-lazy (or eager) OneToMany fields started to generate cryptic errors. A fortunately found forum post gave me the hint to set the fetch attribute to FetchType.LAZY and the error was magically fixed.

Angry sequence generator

We were using a @GeneratedValue which was referencing an automatically generated sequence by hibernate which is called hibernate_sequence.

@Id
@GeneratedValue(
    strategy = GenerationType.SEQUENCE,
    generator = "hibernate_sequence"
)
var id: Long = 0
Enter fullscreen mode Exit fullscreen mode

After migrating to Hibernate 6, this caused issues with the generated ids. After some investigation, my colleague shared an article that helped us fix the issue. Kudos to the author Nicholas Tsim !

After reading the article, we understood that issue was that the allocation size (the range of ids that managed by the Hibernate) which was not in sync between hibernate and the database sequence linked to it.
To put it simply, Hibernate optimizes requests for the next id the sequence by managing a set of ids internally which is called allocationSize.

For example, if the next number of the database sequence is 5 and allocationSize, then Hibernate will internally manage the autoincrement from 5 to 14 (or 15 I'm not sure here :)).
When it reaches the next id, it will request the next database sequence
We fixed this by explicitly specifying the allocation size in code and

Si our code we added a @SequenceGenerator annotation:

@Id
@GeneratedValue(
    strategy = GenerationType.SEQUENCE,
    generator = "registration_id_seq"
)
@SequenceGenerator(
    name = "registration_id_seq",
    sequenceName = "hibernate_sequence",
    allocationSize = 10
)
var id: Long = 0
Enter fullscreen mode Exit fullscreen mode

And in the database we set the increment of the sequence with the same amount of the allocation size.
We achieved this by running this SQL script ALTER SEQUENCE hibernate_sequence INCREMENT BY 10;.
After these changes, our id generation issue was finally fixed !

Conclusion

Upgrading our Quarkus project from version 2 to version 3 went globally very smoothly given the legacy code that we had.
Our pain points were the POM which was not friendly with upgrade tool, and the migration from Hibernate 5 to 6.
But the biggest pain point was the latter.
Thus, the lesson that I learnt is to not underestimate the migration impacts of a dependency which can be bigger than the framework that uses it.

After overcoming all the challenges, our API is now rocking on Quarkus 3 LTS and we are waiting for the next LTS to arrive.

Happy coding !

Top comments (0)