Quarkus migration tips (2022)

Quarkus is one of the most innovative Java projects, therefore it’s not a surprise there are frequent updates to it. In this article we will have a look at the available guidelines to migrate Quarkus applications and tools that can help you to simplify your update strategy.

Migration Overview

When planning a migration of a Quarkus application, you should consider the following aspects:

  • Build file change: Quarkus relies on a Project Object Module (pom) which is a one-stop-shop for all things concerning the project. For minor version changes, simply updating the quarkus.platform.version is often all you need to upgrade your Quarkus application
  • Configuration changes: since the release 1.x, some Quarkus properties have been modified or deprecated. All changes are however documented and you will also get a warning message about the property change
  • API change: The foundation API (Hibernate ORM / Rest / Logging ) are fairly stable therefore we can consider them the Long Term Release of the framework. On the other hand, most innovative API such as Quarkus reactive API have greatly evolved over time and some changes might be needed. Also, the MicroProfile API (which Quarkus fully supports) is being updated regularly. So you need to keep the pace as well.
  • Docker file change: as you know Quarkus is a cloud native framework therefore you have out of the box a set of Dockerfile available to run your application in a container. As you upgrade your framework version, it is a good practice to keep the Dockerfile at pace with it.
  • JVM Change: To build native application, one option is to install locally a GraalVM distribution. From time to time, you will need to upgrade your GraalVM distribution to keep it in sync with your Quarkus version.

With these points in mind, let’s check some strategies to upgrade your Quarkus applications.

Re-create your Quarkus project

If you are moving from a Quarkus 1.x version to the latest 2.x version there are several changes to put in place. To simplify your migration path (Build file change/Docker file change) is it just simpler to recreate your project using the available tools.

Here is a strategy I’m using with success. Use the Maven command dependency:list to collect the list of dependencies:

mvn dependency:list -DexcludeTransitive

[INFO] The following files have been resolved:
[INFO]    io.rest-assured:rest-assured:jar:4.3.2:test
[INFO]    io.quarkus:quarkus-resteasy:jar:1.10.5.Final:compile
[INFO]    io.quarkus:quarkus-jdbc-postgresql:jar:1.10.5.Final:compile
[INFO]    io.quarkus:quarkus-hibernate-orm:jar:1.10.5.Final:compile
[INFO]    io.quarkus:quarkus-junit5:jar:1.10.5.Final:test
[INFO]    io.quarkus:quarkus-resteasy-jsonb:jar:1.10.5.Final:compile

Then, create an application, for example using Quarkus CLI:

quarkus create app

Then, move into the application and add the extensions which we have collected by Maven:

quarkus ext add resteasy jdbc-postgresql hibernate-orm resteasy-jsonb
[SUCCESS] ✅  Extension io.quarkus:quarkus-jdbc-postgresql has been installed
[SUCCESS] ✅  Extension io.quarkus:quarkus-hibernate-orm has been installed
[SUCCESS] ✅  Extension io.quarkus:quarkus-resteasy-jsonb has been installed
 👍  Extension io.quarkus:quarkus-resteasy was already installed

Your project is almost ready. Finish by adding the remaining (non Quarkus) dependencies.

  • Pros: Thanks to the powerful Quarkus CLI, it is fairly simple to recreate a project with updated pom.xml and Dockefiles.
  • Cons: To complete this step, you need to perform some manual steps for external dependencies. You can automate that with some awk/sed scripting although that would be smoother with a maven command that adds a dependency GAV to the pom.xml.

Using Quarkus Maven plugin upgrade goal

By using the Quarkus’ Maven plugin with the update goal you can check whether there are Quarkus-related updates available for a project, such as: new releases of the relevant Quarkus platform BOMs and non-platform Quarkus extensions present in the project.

Here’s a sample output of it:

$ mvn io.quarkus:quarkus-maven-plugin:2.7.5.Final:update
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< org.acme:amqp-demo2 >-------------------------
[INFO] Building amqp-demo2 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- quarkus-maven-plugin:2.7.5.Final:update (default-cli) @ amqp-demo2 ---
[WARNING] quarkus:update goal is experimental, its options and output might change in future versions
[INFO] Looking for the newly published extensions in registry.quarkus.io
[INFO] 
[INFO] Recommended Quarkus platform BOM updates:
[INFO] Update: io.quarkus.platform:quarkus-bom:pom:2.7.4.Final -> 2.7.5.Final

If you are including extensions with a specific version, the update will show the individual changes to apply/remove in your configuration:

[INFO] Recommended Quarkus platform BOM updates: 
[INFO] Update: io.quarkus.platform:quarkus-bom:pom:2.7.1.Final -> {quarkus-version}
[INFO] Update: io.quarkus.platform:quarkus-camel-bom:pom:2.7.1.Final -> {quarkus-version}
[INFO] Remove: io.quarkus.platform:quarkus-kogito-bom:pom:2.7.1.Final  
[INFO]
[INFO] Extensions from io.quarkus.platform:quarkus-bom:
[INFO] Update: io.quarkus:quarkus-resteasy-reactive:2.6.3.Final -> remove version (managed)  
[INFO]
[INFO] Extensions from registry.quarkus.io:
[INFO] Update: io.quarkiverse.prettytime:quarkus-prettytime:0.2.0 -> 0.2.1  
  • Pros: This plugin goal is still experimental however it is being actively developed so in the future you might consider this as the best option
  • Cons: The update tool relies on Quarkus extension registry to check for updates, which is not available for Quarkus 1.x applications. Therefore you need to start from a Quarkus 2.0 version to use this feature.

Using OpenRewrite Maven plugin

OpenRewrite contains several plugins for framework migrations, vulnerability patches, and API migrations with an early focus on the Java language. The wiki which discusses how to set up and run the plugin is available here: https://docs.openrewrite.org/tutorials/quarkus-2.x-migration-from-quarkus-1.x

This is basically what you need to add to your pom.xml:

<project>
  <build>
    <plugins>
      <plugin>
        <groupId>org.openrewrite.maven</groupId>
        <artifactId>rewrite-maven-plugin</artifactId>
        <version>4.20.0</version>
        <configuration>
          <activeRecipes>
            <recipe>org.openrewrite.java.quarkus.quarkus2.Quarkus1to2Migration</recipe>
          </activeRecipes>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.openrewrite.recipe</groupId>
            <artifactId>rewrite-quarkus</artifactId>
            <version>1.1.0</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>

You can run the plugin’s goal with:

mvn rewrite:run

The plug-in will search through the available recipes and perform some changes both in the configuration files and in the source code (when needed):

The plugin will apply the changes directly on your code. Therefore, I’d recommend to perform the migration on top of a Git repository so that you can run a ‘git diff’ before committing the changes.

  • Pros: This is, at the time of writing, the only tool that performs a migration from Quarkus 1.x to Quarkus 2.x . Code and configuration upgrades are quite promising too.
  • Cons: Some limitations exists (check the plugin page). During some tests, I’ve hit some failures in the lifecycle execution. That would deserve more investigation.

Top Migration Changes

After a review of the available tools, let’s check which are the top migration points for a Quarkus application. The main source of information is available in this wiki. Here we will review the most significant aspects.

Changes in application.properties

In terms of configuration, the oldest Quarkus versions use different configuration settings for datasource. See this Quarkus 1.0 application.properties as an example:

quarkus.datasource.url=jdbc:postgresql://${POSTGRESQL_SERVICE_HOST:localhost}:${POSTGRESQL_SERVICE_PORT:5432}/quarkusdb
quarkus.datasource.driver=org.postgresql.Driver
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.initial-size=1
quarkus.datasource.min-size=2
quarkus.datasource.max-size=8

Here is Quarkus 2.x way to configure connection to a Database:

%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=quarkus_test
%prod.quarkus.datasource.password=quarkus_test
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkus_test
%prod.quarkus.datasource.jdbc.max-size=8
%prod.quarkus.datasource.jdbc.min-size=2

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.sql-load-script=import.sql

Firstly, notice that the property quarkus.datasource.url has changed to quarkus.datasource.jdbc.url.

You should also reference the Database dialect with quarkus.datasource.db-kind

Finally, it’s highly recommended to use profiles (such as %prod) so that the remote database is not activated when using development mode.

Next, if your application requires instrumentation-based live reload, check if you are using this old property:

quarkus.dev.instrumentation=true

You need to change it to:

quarkus.live-reload.instrumentation=true

Finally, we account for some other changes in the Microprofile JWT configuration, Quartz and neo4j. The following configuration properties:

smallrye.jwt.sign.key-location=/keys/signing.pem
smallrye.jwt.encrypt.key-location=/keys/encrypt.pem
quarkus.quartz.force-start=true
quarkus.quartz.store-type=db
quarkus.neo4j.pool.metrics-enabled=true

…need to be changed to:

smallrye.jwt.sign.key.location=/keys/signing.pem
smallrye.jwt.encrypt.key.location=/keys/encrypt.pem
quarkus.quartz.start-mode=forced
quarkus.quartz.store-type=jdbc-cmt
quarkus.neo4j.pool.metrics.enabled=true

Changes in Reactive Framework

Quarkus is a reactive framework. In the early product versions a core pillar of Quarkus was ReactiveX, a Java library that lets you create asynchronous and event-based applications using Observable sequences for the Java VM.

ReactiveX uses a set of interfaces (such as Flowable or Observable) to emit notifications and control the flow of items from publishers to subscribers. Example:

public Flowable<Long> apply(Long t1) {
  return Flowable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler);
}

ReactiveX, however, reached EOL in 2021 therefore Quarkus has replaced its implementation with the Mutiny framework.

When using Mutiny, you need to replace ReactiveX interfaces with those available in Mutiny, such as Uni and Multi.

public Multi<Quote> generate() {
  return Multi.createFrom().ticks().every(Duration.ofSeconds(1)).map(n -> generateQuote());
}

Check the Mutiny migration guide to learn how to port your ReactiveX code to Mutiny.

Additionally, some 1+ year old deprecated methods have been removed since Mutiny 1.3.0:

  • Multi.groupItems(): use Multi.group() instead.
  • Multi.transform(): use Multi.select() and Multi.skip() instead.
  • Multi.collectItems(): use Multi.collect() instead.
  • Multi.onOverflow().drop(Consumer): use Multi.onOverflow().invoke(Consumer).drop() instead.
  • Uni.cache() use Uni.memoize(): instead.

Here is a sample of changes from old Mutiny applications:

public class FactorService {

    /* Deprecated
    public static Uni<String> uniGreeting(String name) {
        return Uni.createFrom().item(name)
                .onItem()
                .apply(n -> String.format("Factor this %s", n));
    }
    */
    public static Uni<String> uniGreeting(String name) {
        return Uni.createFrom().item(name)
                .onItem()
                .transform(n -> String.format("Factor this %s", n));
    }
    
     /* Deprecated
    public static Multi<String> greetings(int count, String name) {
        return Multi.createFrom().ticks().every(Duration.ofMillis(1))
                .onItem()
                .transform(n -> String.format("hello %s - %d", name, n))
                .transform()
                .byTakingFirstItems(count);
    }
    */
    public static Multi<String> greetings(int count, String name) {
        return Multi.createFrom().ticks().every(Duration.ofMillis(1))
                .onItem()
                .transform(n -> String.format("hello %s - %d", name, n))
                .select()
                .first(count);
    }
    
     /* Deprecated
    public static Uni<List<String>> collectItems(int count, String name) {
        Multi<String> multi = greetings(count, name);
        Uni<List<String>> uni = multi
                .collectItems()
                .asList();
        Executor e = command -> {
            System.out.print("something");
        };
        multi.subscribeOn(e);
        uni.subscribeOn(e);
        return uni;
    }
    */
     public static Uni<List<String>> collectItems(int count, String name) {
        Multi<String> multi = greetings(count, name);
        Uni<List<String>> uni = multi
                .collect()
                .asList();
        Executor e = command -> {
            System.out.print("something");
        };
        multi.runSubscriptionOn(e);
        uni.runSubscriptionOn(e);
        return uni;
    }
    
       
     /* Deprecated
    public static MultiCollect<Long> hotStreamGreetings(int count, String name) {
        return Multi.createFrom().ticks().every(Duration.ofMillis(1))
                .transform()
                .toHotStream()
                .collectItems();
    }
    */
    
    public static MultiCollect<Long> hotStreamGreetings(int count, String name) {
        return Multi.createFrom().ticks().every(Duration.ofMillis(1))
                .toHotStream()
                .collect();
    }
}

Finally, since Quarkus 2.0, the Vert.x API uses the Jackson Mapper managed by Quarkus. Thus, instead of configuring the JSON object mappers on the Json class, customize the Jackson Mapper following the instructions from https://quarkus.io/guides/rest-json#configuring-json-support.

Qute framework

Qute is a front-end framework for Quarkus applications. Since its first release of this extensions, some annotation have changed. You should account for the following changes:

  • Replace @io.quarkus.qute.api.CheckedTemplate with @io.quarkus.qute.CheckedTemplate
  • Replace @io.quarkus.qute.api.ResourcePath with @io.quarkus.qute.Location.

Example:

public class ReportGenerator {
 /* Deprecated
    @ResourcePath("reports/v1/report_01")
 */
    @Location("reports/v1/report_01")
    
    Template report;
    
    @Scheduled(cron = "0 30 * * * ?")
    void generate() {
        String result = report
                .data("samples", new Object())
                .render();
    }
}
/* Deprecated
@io.quarkus.qute.api.CheckedTemplate
*/
@io.quarkus.qute.CheckedTemplate
class HelloTemplate {
    public static native TemplateInstance hello(String name);
}

Hibernate ORM with Panache

The getEntityManager() and the flush() methods of PanacheEntityBase are now static methods.
With regards to Hibernate reactive with Panache, the persist() and persistAndFlush() methods now return an Uni instead of an Uni to allow chainning the methods.

Removal of the native-image Maven goal

The goal native-image of quarkus-maven-plugin had long been deprecated and the plugin had been logging a warning since 1.11.1.Final.

It has now finally been removed in 1.13. Please remove the plugin execution from your pom.xml and simply add the following property to your native profile:

<properties>
    <quarkus.package.type>native</quarkus.package.type>
</properties>

Conclusion

This was a walk-though the changes you need to apply in order to migrate your Quarkus applications to the latest versions. As this is a quite active topic, I will keep this tutorial up-to-date with all news and upcoming tools to ease the migration.

Found the article helpful? if so please follow us on Socials