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">
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.
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.
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.
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.
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.
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:
- Find the complete source code for the example application on GitHub.
- See the Quarkus Configuration Guide to learn how to configure your application using the Quarkus
ConfigProperty
,application.properties
, and so on. - Learn more about the MongoDB Panache extension. Also, see the PanacheMongoRepositoryBase repository on GitHub—reading source code is what developers do right!
- Get more information about building a native Quarkus executable.