How to migrate Thorntail applications

As you probably known, the Thorntail project has come to an end of life. In this article we will learn how to move your project to a suitable Enterprise environment.

First of all, what does it mean the End of Life for Thorntail? In short, there won’t be new releases after the 2.7.0.Final. There might be a bug fixing release if a critical bug is found in the next months.

Please note that this announcement only concerns the Thorntail community project and does not apply directly to the Red Hat build of Thorntail. Red Hat will continue providing support for the entire lifecycle of the product.

Let’s see which are the recommended migration paths for your applications.

Scenario 1) Legacy applications

If your application uses some API which are considered “legacy” such as EJB, JSF, SOAP Web services, CORBA and the costs of redesigning or replacing the system are prohibitive in the near term, then probably the best option is to move to WildFly. Or better to say, you can move to one of the following options:

Standard / Custom WildFly distribution

If your application is designed to be executed in a runtime environment, which is shared between multiple applications, then the best option is to configure your application for WildFly server. You can also create a custom WildFly distribution if you don’t need all the layers that the standard WildFly distribution provides. Check this tutorial for learning how to use Galleon to create a custom WildFly distribution: Provisioning WildFly with Galleon

As a proof of concept, let’s port this sample Thorntail application which uses JSF and EJB dependencies to a WildFly standard distribution: https://github.com/fmarchioni/practical-enterprise-development/tree/thorntail/code/jsf/thorntail-jsf-basic

This application uses an assortment of technologies which require the following dependencies to work:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.itbuzzpress.jsf</groupId>
  <artifactId>thorntail-jsf-basic</artifactId>
  <version>1.0</version>
  <packaging>war</packaging>
  <name>Thorntail JSF Basic example</name>
  <properties>
    <version.thorntail>2.4.0.Final</version.thorntail>
    <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.thorntail</groupId>
        <artifactId>bom-all</artifactId>
        <version>${version.thorntail}</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.thorntail</groupId>
      <artifactId>ejb-remote</artifactId>
    </dependency>
    <dependency>
      <groupId>io.thorntail</groupId>
      <artifactId>cdi</artifactId>
    </dependency>
    <dependency>
      <groupId>io.thorntail</groupId>
      <artifactId>ejb</artifactId>
    </dependency>
    <dependency>
      <groupId>io.thorntail</groupId>
      <artifactId>jsf</artifactId>
    </dependency>
  </dependencies>
  <build>
    <finalName>${project.artifactId}</finalName>
  </build>
</project>

There are no other ties with Thorntail, except for the io.thorntail dependencies included in pom.xml. So, all you have to do is rewriting your pom.xml to include WildFly distribution.

Here is the WildFly project: https://github.com/fmarchioni/practical-enterprise-development/tree/master/code/jsf/ee-jsf-basic

Following here is the pom.xml which uses WildFly BOM and Jakarta EE 8 to manage the dependencies.

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.itbuzzpress.jsf</groupId>
  <artifactId>ee-jsf-basic</artifactId>
  <version>1.0</version>
  <packaging>war</packaging>
  <name>Basic JSF example</name>
  <properties>
    <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <version.server.bom>20.0.0.Final</version.server.bom>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.wildfly.bom</groupId>
        <artifactId>wildfly-jakartaee8-with-tools</artifactId>
        <version>${version.server.bom}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
        <groupId>jakarta.enterprise</groupId>
        <artifactId>jakarta.enterprise.cdi-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jakarta.validation</groupId>
      <artifactId>jakarta.validation-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.spec.javax.annotation</groupId>
      <artifactId>jboss-annotations-api_1.3_spec</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.spec.javax.ejb</groupId>
      <artifactId>jboss-ejb-api_3.2_spec</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.spec.javax.faces</groupId>
      <artifactId>jboss-jsf-api_2.3_spec</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>${project.artifactId}</finalName>
  </build>
</project>

Somethimes the Thorntail applicaiton includes the definition of resources via the project-defaults.yml. This file can define resources such as Datasources or JMS destinations. Here is an example:

thorntail:
  messaging-activemq:
    servers:
      default:
        jms-queues:
          exampleQueue:
            entries:
            - jms/queue/exampleQueue
            - java:jboss/exported/jms/queue/exampleQueue

In this case, you need to port the Thorntail configuration into WildFly configuration. For example, the above configuration can be ported as follows in your WildFly profile:

 <jms-queue name="ExampleQueue" entries="queue/exampleQueue java:/jboss/exported/jms/queue/exampleQueue"/>

WildFly bootable JAR file

The other option, which mimics the Thorntail approach, is to use the WildFly bootable JAR file which has been recently annuonced by the WildFly team.

In order to do that, you have to transform your pom.xml file as discussed in the previous section (from “io.thorntail” to “jakarta.enterprise”). Then add the wildfly-jar-maven-plugin to the pom.xml as follows:

<plugin>
  <groupId>org.wildfly.plugins</groupId>
  <artifactId>wildfly-jar-maven-plugin</artifactId>
  <version>2.0.0.Beta1</version>
  <configuration>
    <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#${version.server.bom}</feature-pack-location>
    <layers>
      <layer>jaxrs</layer>
      <layer>management</layer>
    </layers>
    <excluded-layers>
      <layer>deployment-scanner</layer>
    </excluded-layers>
    <hollow-jar>true</hollow-jar>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>package</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Additionally, your server configuration can be customized via the “configuration” element which lets you customize your WildFly configuration by loading some CLI scripts:

<plugin>
    <groupId>org.wildfly.plugins</groupId>
    <artifactId>wildfly-jar-maven-plugin</artifactId>   
    <configuration>
        <cli-script-files>
            <script>../scripts/logging.cli</script>
        </cli-script-files>
        <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#${version.wildfly}</feature-pack-location>
        <layers>
            <layer>jaxrs</layer>
            <layer>logging</layer>
            <layer>management</layer>
        </layers>
        <excluded-layers>
            <layer>deployment-scanner</layer>
        </excluded-layers>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>package</goal>
            </goals>
        </execution>
    </executions>
</plugin>

You can learn more about WildFly bootable JAR in this tutorial: Turn your WildFly applications in bootable JARs

Scenario 2) Microservice applications

If your application has been designed using Microservices principles (for example using the Twelve factor methodology https://12factor.net/) and relies on technologies such as REST Services, Hibernate/JPA, Microprofile API, CDI then Quarkus should be a perfect fit for most Thorntail applications. Quarkus includes many of the same libraries and APIs, such as JAX-RS with RESTEasy, JPA with Hibernate, JTA with Narayana, MicroProfile with SmallRye and many more. Quarkus also offers an awesome development mode and ahead of time compilation to native binaries using GraalVM.

In terms of configuration, the first change to your pom.xml is the imported pom file, which should change from:

    <dependency>
        <groupId>io.thorntail</groupId>
        <artifactId>bom</artifactId>
        <version>${version.io.thorntail}</version>
        <scope>import</scope>
        <type>pom</type>
    </dependency>

to:

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-bom</artifactId>
        <version>${io.quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>

Then, you have to migrate the single “io.thorntail” dependencies to the matching “io.quarkus“. For example:

    <dependency>
      <groupId>io.thorntail</groupId>
      <artifactId>jaxrs</artifactId>
    </dependency>

should be migrated to:

     <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>        <
    </dependency>

Below I’m copying some dependencies which are commonly used in Thorntail and the corresponding Quarkus dependency:

Thorntail Quarkus
 io.thorntail:undertow  io.quarkus:quarkus-undertow
 io.thorntail:jaxrs  io.quarkus:quarkus-resteasy
 io.thorntail:jpa  io.quarkus:quarkus-hibernate-orm
 io.thorntail:jsonp  io.quarkus:quarkus-jsonp
 io.thorntail:jsonb  io.quarkus:quarkus-jsonb
 io.thorntail:microprofile-openapi  io.quarkus:quarkus-smallrye-openapi
 io.thorntail:microprofile-opentracing  io.quarkus:quarkus-smallrye-opentracing
 io.thorntail:microprofile-metrics  io.quarkus:quarkus-smallrye-metrics
 io.thorntail:microprofile-health  io.quarkus:quarkus-smallrye-health
io.thorntail:microprofile-fault-tolerance io.quarkus:quarkus-smallrye-fault-tolerance
io.thorntail:microprofile-jwt io.quarkus:quarkus-smallrye-jwt

Application Configuration changes

In terms of configuration, Quarkus adopts a MicroProfile Config compatible approach which defaults to the application.properties file as main reference for your configuration.

Therefore, you will have to find the equivalent configuration. This is not a too scary task. For example, it is possible to generate an example application.properties with all known configuration properties, to make it easy to see what Quarkus configuration options are available. To do this, run:

$ ./mvnw quarkus:generate-config

As an example of configuration migration from Thorntail to Quarkus, here is a sample Datasource definition in Thorntail’s project-defaults.yml:

thorntail:
  datasources:
    jdbc-drivers:
      myh2:
        driver-class-name: org.h2.Driver
        xa-datasource-name: org.h2.jdbcx.JdbcDataSource
        driver-module-name: com.h2database.h2

And this is the equivalent in Quarkus:

quarkus.datasource.url=jdbc:h2:file:/opt/h2/database.db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
quarkus.datasource.driver=org.h2.Driver
quarkus.datasource.username=sa
quarkus.datasource.password=sa
quarkus.datasource.max-size=8
quarkus.datasource.min-size=2
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.log.sql=false

For an introduction to Quarkus, we recommend checking this tutorial: Getting started with QuarkusIO

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