Quarkus

While Spring Boot has long been the de-facto framework for developing container-based applications in Java, the performance benefits of a Kubernetes-native framework are hard to ignore. In this article, I will show you how to quickly migrate a Spring Boot microservices application to Quarkus. Once the migration is complete, we'll test the application and compare startup times between the original Spring Boot application and the new Quarkus app.

Note: For developers interested in migrating from Spring Boot to Quarkus, it's important to know that Quarkus does not support all of Spring Boot's extensions and features. As one example, it only supports a subset of the Java EE Contexts and Dependency Injection (CDI) API. Migrating microservices or container-based applications to Quarkus will be easier than migrating monolithic ones.

About Quarkus

Quarkus is a Kubernetes-native Java framework tailored for Java virtual machines (JVMs) such as GraalVM and HotSpot. Being Kubernetes-native means that Quarkus takes a container-first approach to Java application development. The smaller footprint inherent in container-first development makes Quarkus one of the best options for running Java applications on Kubernetes and serverless platforms today.

The Spring Boot application

For our example application, we'll use the AccountBalance microservices application from my previous article, Event-based microservices with Red Hat AMQ Streams. The AccountBalance service has its own MongoDB database, which holds account-balance information. The database is also called by other services, such as the EventCorrelator service.

You can find the source code for the example application on GitHub. I'll guide you through each step of migrating this application from Spring Boot to Quarkus.

The following shows the content of the sample source code. This is typical standard Java project file structure. mvnw is Maven wrapper plugin that we generated. We will need to modify the pom.xml and the source codes under the src/main.

Sample Codes Content
Sample Codes Content
img class=

Sample Codes Content">

 

Step 1: Modify the pom.xml for your application

The simplest way to migrate from Spring Boot to Quarkus is to bootstrap a sample Quarkus application and use that application's pom.xml as a template for modifying the same file in your Spring Boot application.

Note: In addition to the example pom.xml, the Quarkus team provides a web-based user interface (UI) that you can use for migration. I won't demonstrate using the web UI in this article.

Take a minute to set up your terminal and project, as described in the bootstrapping instructions. Once you have your project set up, we can begin modifying the Spring Boot pom.xml. We'll start by removing Spring Boot configurations we no longer need, then we'll replace those elements with the corresponding configurations for a Quarkus app.

Remove the Spring Boot configurations

First, we remove the packing configuration from the example application's pom.xml. This no longer needed because it is taken care by the <build> part in the pom.xml configuration.

<!--- Remove the packing configuration -->

<packaging>jar</packaging>

spring-boot-starter-parent is for Spring Boot application which is no longer needed here.

<!--- Remove spring-boot-starter-parent -->

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.2.RELEASE</version>
</parent>

same goes to spring-cloud-dependencies:

<!-- Remove the following from the <dependencyManagement> -->

<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR4</version>

Now, we can remove all of the remaining Spring Boot dependencies:

<!-- Remove all Spring Boot-related dependencies -->

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-mongodb</artifactId>
 </dependency>
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
  <groupId>javax.ws.rs</groupId>
  <artifactId>javax.ws.rs-api</artifactId>
  <version>2.0.1</version>
</dependency>
<dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-model</artifactId>
  <version>3.3.9</version>
</dependency>
<dependency>
  <groupId>javax.xml.bind</groupId>
  <artifactId>jaxb-api</artifactId>
  <version>2.2.11</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-core</artifactId>
  <version>2.2.11</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.2.11</version>
</dependency>
<dependency>
  <groupId>javax.activation</groupId>
  <artifactId>activation</artifactId>
  <version>1.1.1</version>
</dependency>

We can also take out the spring-boot-maven-plugin in the build section:

<!-- Remove the spring-boot-maven plugin from the build section -->

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

Add Quarkus elements to the pom.xml

Next, we'll copy the properties and dependencies from the bootstrapped Quarkus pom.xml and paste them in at the top of the pom.xml for our example application. This section is important to tell the compiler which version of components that we want to use for our Quarkus. Quarkus release is moving fast, you may want to check the latest version for <quarkus.platform.version>

<!-- Place this at the top of the pom.xml -->

<properties>
  <compiler-plugin.version>3.8.1</compiler-plugin.version>
  <maven.compiler.parameters>true</maven.compiler.parameters>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <quarkus-plugin.version>1.2.1.Final</quarkus-plugin.version>
  <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
  <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
  <quarkus.platform.version>1.2.1.Final</quarkus.platform.version>
  <surefire-plugin.version>2.22.1</surefire-plugin.version>
</properties>

Add this under dependencyManagement:

<!-- Add this under dependencyManagement -->

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>${quarkus.platform.group-id}</groupId>
      <artifactId>${quarkus.platform.artifact-id}</artifactId>
      <version>${quarkus.platform.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Now we can copy the Quarkus dependencies from the bootstrap file and paste them into the pom.xml. Note that in the code below, I also manually added the MongoDB Panache dependency, which the example application requires. I added this dependency manually because I did not use the web UI to bootstrap it in earlier. Note, also, that AccountBalance uses a repository to connect to and query the MongoDB database. The Panache extension allows us to migrate the existing code with minimal changes. (You can find the source code for Panache on GitHub.)

Start by adding these dependencies to the pom.xml:

<dependency>
  <groupId>io.quarkus</groupId>

  <!-- notice this app is using jsonb -->

  <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-junit5</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>io.rest-assured</groupId>
  <artifactId>rest-assured</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-mongodb-panache</artifactId>
</dependency>

Add the quarkus-maven-plugin under the build configuration:

<!-- Add the following plugin under the build configuration -->

<build>
  <finalName>${project.artifactId}-${project.version}</finalName>
  <plugins>
    <plugin>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-maven-plugin</artifactId>
      <version>${quarkus-plugin.version}</version>
      <executions>
        <execution>
          <goals>
            <goal>build</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
        <version>${compiler-plugin.version}</version>
    </plugin>
    <plugin>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>${surefire-plugin.version}</version>
      <configuration>
        <systemProperties>
          <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
        </systemProperties>
      </configuration>
    </plugin>
  </plugins>
</build>

Finally, add the profiles settings:

<!-- Add the following profiles settings -->

<profiles>
  <profile>
    <id>native</id>
    <activation>
      <property>
        <name>native</name>
      </property>
    </activation>
    <build>
      <plugins>
        <plugin>
          <artifactId>maven-failsafe-plugin</artifactId>
            <version>${surefire-plugin.version}</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                  <goal>verify</goal>
                </goals>
                <configuration>
                  <systemProperties>
                    <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                  </systemProperties>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
     </build>
     <properties>
       <quarkus.package.type>native</quarkus.package.type>
     </properties>
  </profile>
</profiles>

Step 2: Migrate the Spring Boot application code

The Maven POM is all set. We're now ready to migrate the Spring Boot application code to Quarkus.

First, remove the Application.java class. We don't need it anymore. You can also modify the application.properties for the application port and the MongoDB client properties files, as shown here:

### --- Remove the Spring Boot properties
# spring.application.name=accountbalance-service
# spring.data.mongodb.host=localhost
# spring.data.mongodb.port=27017
# spring.data.mongodb.username=checkbalance
# spring.data.mongodb.password=checkbalance
# spring.data.mongodb.database=checkbalance
# server.port=8082
### --- Replace with equivalent Quarkus MongoDB properties
quarkus.mongodb.connection-string=mongodb://checkbalance:checkbalance@localhost:27017/checkbalance
# --- Note the wrong context of database name ... planning and design before coding is so important.
quarkus.mongodb.database=checkbalance
quarkus.http.port=8082

The entity bean code

Next, we'll change the entity bean code in Balance.java. To start, remove the Spring Boot-related import statements. We are going to replace these with Quarkus equivalent components in the next section.

/// --- Remove the Spring Boot-related import statements

import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

Add the equivalent import statements for Quarkus. Quarkus framework provides its own supported components for MongoDB via the MongoDB Panache extension. Quarkus also provides its own version of CDI annotation. This where we provide the correct import statements for those.

/// --- Add the following Quarkus-related import statements

import io.quarkus.mongodb.panache.MongoEntity;
import io.quarkus.mongodb.panache.PanacheMongoEntity;
import org.bson.codecs.pojo.annotations.BsonProperty;
import org.bson.codecs.pojo.annotations.BsonId;
import io.quarkus.mongodb.panache.PanacheQuery;

Replace the existing @Document annotation with @MongoEntity:

/// --- Replace @Document annotation with @MongoEntity

// --- @Document(collection = "balance") <--- Remove this
@MongoEntity(collection="balance")
public class Balance extends PanacheMongoEntity{
/// --- More codes here are omitted ...
...

Replace Spring Boot's @Id annotation with Quarkus's @BsonId:

/// --- Replace @Id annotation with @BsonId

// @Id <--- Remove this
@BsonId  //In fact, we do not need this. Just leave it here for now.
private String _id;

The repository class

We also need to modify the Spring Boot repository class, BalanceRepository.java. To start, remove the following import statement:

/// --- Remove the following import statement

import org.springframework.data.mongodb.repository.MongoRepository;

Replace it with this one:

/// --- Add the following import statements

import io.quarkus.mongodb.panache.PanacheMongoRepository;
import javax.enterprise.context.ApplicationScoped;
import io.quarkus.mongodb.panache.PanacheQuery;

Then make three more quick changes:

/// --- Change the BalanceRepository to implement PanacheMongoRepository
// --- Change the BalanceRepository from interface to class
// --- Drop in the @ApplicationScoped annotation

@ApplicationScoped
public class BalanceRepository implements PanacheMongoRepository<Balance> {
   // --- Change the findByAccountId(String accountId) to the following implementation
   public Balance findByAccountId(String accountId){
      return find("accountId", accountId).firstResult();
   }
}

The REST class

We'll also modify AccountBalance.java. Start by removing all of the Spring Boot-related import statements:

/// --- Remove all the Spring Boot-related import statements

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Example;
import org.springframework.data.repository.Repository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Autowired;

Add the following import statements for Quarkus. (We're not using all of these imports in the .java files, but we can leave them for now.)

/// --- Add the following import statements.

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.MediaType;
import javax.inject.Inject;

Remove the Spring Boot @RequestMapping and @RestController annotations:

/// --- Remove the @RequestMapping and @RestController and
update as follows

// @RequestMapping("/ws/pg")      <--- Remove this
// @RestController      <--- Remove this
@Path("/ws/pg")         // <---- Add this
@Produces(MediaType.APPLICATION_JSON)         // <---- Add this
@Consumes(MediaType.APPLICATION_JSON)        // <---- Add this
public class AccountBalance{
/// --- I have omitted more code here
...

You will also need to remove the @Autowire and @Inject annotations:

/// --- Remove @Autowire with @Inject

// @Autowired      <--- Remove this
@Inject
private BalanceRepository repository;

Note that we changed the @RequestMapping annotation to @Path, and we also removed @ResponseBody. As our last step, we need to change the @PathVariable to @PathParam, as shown below. (While not shown, we would apply similar changes to all the other methods in this Java class. See the example application's source code to review all of the changes):

@Path("/balance/{accountid}")
@GET
public Balance get(@PathParam("accountid") String accountId) {
   Balanceresult = repository.findByAccountId(accountId);
   return result;
}

That completes the migration, although I left out some steps to keep this exercise brief. As an example, the AccountBalance application is meant to be deployed onto Red Hat OpenShift, so I modified the Heathz.java file for that. You can view those changes in the application source.

Step 3: Test the Quarkus application

Next, we'll test our new application. Figure 1 shows the Spring Boot execution before migration.

SpringBoot at command prompt
SpringBoot at command prompt

Figure 1. The Spring Boot app starts within 3.166 seconds.">

Let's see how the Quarkus application's startup compares to Spring Boot. From the command prompt, switch over to the AccountBalance directory. Execute the following command to run the migrated application:

mvn quarkus:dev

Note that the first time you run the application, it can take longer than it will on subsequent runs. Maven needs additional time to download the Quarkus repositories to your local machine. Figure 2 shows the start time for the new Quarkus application.

A screenshot of the Quarkus application's start time on first execution.
Quarkus execution

Figure 2. Quarkus starts within 1.540 seconds.">

That's an improvement. Now let's do a Quarkus-native build. Remember to execute the native build from the project's root directory:

/mvnw package -Pnative

The output in Figure 3 indicates a successful Quarkus-native build.

Quarkus native build completed sucessfully
Quarkus native build completed sucessfully

Figure 3. The Quarkus-native build completes successfully.">

Enter the following to run the native binary:

./target/account-service-1.0.0-runner

Figure 4 shows the execution time for the Quarkus-native build.

A screenshot of the execution time for the Quarkus native build.
Quarkus native execution

Figure 4. The Quarkus-native build starts within 0.071 seconds.">

Finally, I created a simple UI to call the AccountBalance service and display an account balance. As shown in Figure 5, the migrated application works as expected.

An account balance is displayed in the AccountBalance app's UI.
The account balance is shown on the UI apps

Figure 5. The AccountBalance application works as it should.">

The migration from Spring Boot to Quarkus was a success.

Conclusion

Migrating the example application from Spring Boot to Quarkus was a pretty straightforward exercise. For a more complicated application, the migration path would be more complex. The biggest challenge with migrating my microservices application from Spring Boot to Quarkus was determining which annotations and libraries to use. The documentation for Quarkus provides good examples but omits import statements. Many IDEs will handle those configuration details for you, but I chose to migrate manually.

I hope this article serves as a starting point for migrating your Java applications from Spring Boot to Quarkus. If you want to take what you've learned to the next level, you could follow these steps to containerize your application.

Resources

Check out the following resources to learn more:

Last updated: May 30, 2023