Want to learn more about developing applications with Quarkus? Download our free ebook Quarkus for Spring Developers, which helps Java developers familiar with Spring make a quick and easy transition.
Quarkus is a Java stack tailored for OpenJDK HotSpot (or OpenJ9 on zSeries) and GraalVM, crafted from optimized Java libraries and standards. It is a good choice for building highly-scalable applications while using lower amounts of CPU and memory resources than other Java frameworks. These applications can be traditional web applications, serverless applications, or even functions as a service.
There are many documented instances of organizations migrating their applications to Quarkus. In this article, let's see one such migration path from Spring Boot to Quarkus that is part magic and part madness! The magic will be some hand waving and performing the migration without changing a single line of code. The madness will be trying to figure out how it was done.
The Application
The application is a simple “to-do” task management system. The user can enter to-do items and then check them off once done. These items are stored in a PostgreSQL database. All the application’s source code can be found here. There’s a version that uses Gradle instead of Maven as a build tool on the gradle
branch.
Start the database
The application requires a PostgreSQL database, so the first thing we will do is use Docker or Podman to start an instance locally:
docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name tododb -e POSTGRES_USER=todo -e POSTGRES_PASSWORD=todo -e POSTGRES_DB=tododb -p 5432:5432 postgres:13
A PostgreSQL 11.5 instance on port 5432 should now be running. The tododb
schema accessible by the user todo
with the password todo
should be created.
Run the application
Run the application by issuing the command ./mvnw clean spring-boot:run
. If you want to use Gradle instead of Maven, first switch to the gradle
branch (git checkout gradle
) and run the command ./gradlew clean bootRun
.
You should see the standard Spring Boot banner:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.5.2) INFO 70823 --- [ restartedMain] i.q.todospringquarkus.TodoApplication : Started TodoApplication in 4.392 seconds (JVM running for 5.116)
Take note of the startup time. We'll revisit this in a bit.
Once the application is running, navigate to http://localhost:8080 in your favorite browser. You should see the main application screen as shown here in Figure 1.
Play around with the application a bit. Type a new todo into the text box and press Enter. That todo will show up in the list, as seen here in Figure 2.
- Click the empty circle next to a todo to complete it, or uncheck it to mark it as incomplete.
- Click the X to remove a todo.
- The OpenAPI link at the bottom of the page will open the OpenAPI 3.0 specification for the application.
- The Swagger UI link opens the embedded Swagger UI, which can be used to execute some of the RESTful endpoints directly.
- The Prometheus Metrics link leads to the Prometheus metrics endpoint, which would be scraped intermittently by Prometheus.
- The Health Check link opens the built-in health check exposed by Spring Boot.
Go ahead and play around a bit to see it all in action. Don’t forget to come back here once you’re done! Use CTRL-C
on your keyboard to stop the application once you’re done.
Examine the internals
The application is a full-featured Spring Boot application using the following capabilities:
- Spring MVC for building a REST layer:
- Open
src/main/java/io/quarkus/todospringquarkus/TodoController.java
to find the Spring MVC RESTful controller, exposing the various endpoints available to the user interface.
- Open
- Spring Data JPA for defining relational entities as well as storing and retrieving them:
- Open
src/main/java/io/quarkus/todospringquarkus/TodoEntity.java
to find the Java Persistence API (JPA) entity, representing the relational table for storing the todos. - Open
src/main/java/io/quarkus/todospringquarkus/TodoRepository.java
to find the Spring Data JPA Repository, exposing all of the create, read, update, and delete operations for the TodoEntity.
- Open
- Spring Boot Actuators for providing operational capabilities, including health checks and metrics gathering.
- SpringDoc OpenAPI 3 for generating and exposing RESTful API information as well as the embedded Swagger UI endpoint.
- Prometheus Micrometer Registry for exposing metrics to Prometheus.
- Open
src/main/resources/META-INF/resources
to find the user interface components used.
Configuration
Open src/main/resources/application.properties
to find the application configuration:
spring.jpa.hibernate.ddl-auto=create-drop spring.datasource.url=jdbc:postgresql://localhost:5432/tododb spring.datasource.username=todo spring.datasource.password=todo springdoc.api-docs.path=/openapi springdoc.swagger-ui.path=/swagger-ui management.endpoints.web.exposure.include=prometheus,health
Open src/main/resources/import.sql
to find some SQL that will pre-populate the database table with an initial set of data:
INSERT INTO todo(id, title, completed) VALUES (0, 'My first todo', 'true');
That’s it Not a lot of code here for a fully-functional application that’s a whole lot more than “Hello World!”
The Magic
One hard requirement for this migration has been chosen: The application’s source code cannot be modified in any way.
Are you ready for the magic trick? Return to the command line and run the command ./.mvnw clean spring-boot:run
. If you want to use Gradle instead of Maven, first switch to the gradle
branch (git checkout gradle
) and run the command ./.gradlew clean bootRun
.
The first thing you’ll notice is the Quarkus banner and startup messaging instead of the Spring Boot one:
__ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ INFO [io.quarkus] (Quarkus Main Thread) todo-spring-quarkus 0.0.1-SNAPSHOT on JVM (powered by Quarkus 2.0.0.Final) started in 2.748s. Listening on: http://localhost:8080 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, kubernetes, micrometer, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-health, smallrye-openapi, spring-data-jpa, spring-di, spring-web, swagger-ui]
Wait, what just happened? The application is now a Quarkus application and no longer a Spring Boot application? That's madness!
Want proof? Go back to your browser window (http://localhost:8080 in case you closed it) and reload the page. The same user interface is there and is completely functional. Click on all the various links at the bottom of the page. They’re all completely functional as they were before.
Also, note the startup time. The Quarkus version starts up in almost half the amount of time given the exact same codebase (4.392s for Spring vs. 2.748s for Quarkus in the shown examples). That's Supersonic, Subatomic, Java!
Nothing changed, so how did all this happen? A good magician doesn’t reveal his or her secrets!
The Madness
All good magicians use sleight-of-hand to distract his or her audience when performing a trick. There are a few things that weren’t shown yet in this post that are hidden from the naked eye. Did you notice anything suspicious? Let’s take a closer look at how the trick was done.
Execution
Look very closely at the commands you used to run the application:
Using Maven:
Spring Boot: ./mvnw clean spring-boot:run
Quarkus: ./.mvnw clean spring-boot:run
Using Gradle:
Spring Boot: ./gradlew clean bootRun
Quarkus: ./.gradlew clean bootRun
Notice any differences? A different executable (.mvnw
/.gradlew
) is used in the Quarkus version. If you open those files and examine them closely, you’ll notice some trickery going on:
#!/bin/sh ./mvnw clean quarkus:dev -Pquarkus
#!/bin/sh ./gradlew -Pprofile=quarkus $@
We used some sleight-of-hand to disguise the actual commands used to start the application. More on Gradle and Maven profiles later.
Configuration
Are you saying that Quarkus knows how to read and understand the Spring Boot configuration inside src/main/resources/application.properties
? Well, no. Remember, this is a magic trick. A magician never tells the complete truth. What wasn’t shown in the example above is the Quarkus-specific configuration. It was hidden further down in the file.
If you re-open src/main/resources/application.properties
and scroll all the way to the bottom (around line 58) you’ll see some additional configuration:
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/tododb quarkus.datasource.username=todo quarkus.datasource.password=todo quarkus.datasource.metrics.enabled=true quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.sql-load-script=import.sql quarkus.swagger-ui.always-include=true quarkus.swagger-ui.path=/swagger-ui quarkus.micrometer.export.prometheus.path=/actuator/prometheus quarkus.smallrye-health.root-path=/actuator/health quarkus.smallrye-openapi.path=/openapi
This configuration is similar to the Spring Boot configuration at the top of the file. One thing it does do, however, is to re-define the paths for the Prometheus and the health probe endpoints, so they match the paths of the Spring Boot actuator endpoints. This allows the Prometheus Metrics and Health Check links at the bottom of the screen to work without any changes to the user interface.
Pretty sneaky, huh?
Dependencies
As you can imagine, there are lots of dependencies that need to be changed, added, or updated. Luckily, Quarkus provides many Spring compatibility extensions:
- Quarkus extension for Spring Dependency Injection
- Quarkus extension for Spring Web
- Quarkus extension for Spring Data JPA
- Quarkus extension for Spring Boot Properties
Simply swapping some of the Spring Boot dependencies for the Quarkus ones will go a long way. There are a few other capabilities, such as Prometheus metrics, OpenAPI documentation, Swagger UI integration, and health checks that need other dependencies added. Lucky for us again, Quarkus has these capabilities as well:
- Quarkus extension for OpenAPI and Swagger UI
- Quarkus extension for MicroProfile Health
- Quarkus extension for Micrometer Metrics
Now you’re probably thinking to yourself, “But I didn’t make ANY changes whatsoever. All I did was run a Maven or Gradle command, and the entire application ran as a Quarkus application instead of a Spring Boot application. How can that be?”
Build setup
The build file is where all the magic happens. The build tool you are using determines how the dependency resolution magic actually happens, though.
Maven
On the main
branch, open the pom.xml
file. You’ll immediately notice the build file is broken into multiple Maven profiles, spring
and quarkus
, with the spring
profile being the default profile. These profiles define which dependencies and build plugins are included when you run either ./mvnw clean spring-boot:run
or ./mvnw -Pquarkus clean quarkus:dev
(which is disguised using some redirection in the aforementioned .mvnw
script).
Gradle
Gradle, on the other hand, does not have the concept of a profile. You’ll notice a handful of .gradle
files in the gradle
branch of the project:
build-common.gradle
- Contains any common build logic applicable to both versions of the application, including setting the groupId/version of the produced artifact as well as artifact repository definitions.
build-spring.gradle
- Contains all build logic, plugins, and dependencies specific to the Spring version of the application.
build-quarkus.gradle
- Contains all build logic, plugins, and dependencies specific to the Quarkus version of the application.
build-quarkus.gradle
even introduces Spring Boot’sbootRun
task, re-mapping it to thequarkusDev
task from the Quarkus Gradle plugin.
- Contains all build logic, plugins, and dependencies specific to the Quarkus version of the application.
settings.gradle
- Where the magic actually happens! The
settings.gradle
file looks for a Gradle project property calledprofile
:- If this property isn’t found, it defaults the value to
spring
. - It then sets the project’s build file, either
build-spring.gradle
orbuild-quarkus.gradle
, as the main build file to use.
- If this property isn’t found, it defaults the value to
- Where the magic actually happens! The
In general, this is a pretty good pattern to use if you need Gradle to simulate the capabilities of Maven profiles.
Application Main Class
It was mentioned at the beginning of this post that we wanted to perform the migration without changing a single line of code. Every Spring Boot application needs to have an “application” class that contains a main
method and is annotated with @SpringBootApplication
. In our project, src/main/java/io/quarkus/todospringquarkus/TodoApplication.java
is that class.
Quarkus does not require such a class, nor do any of the Quarkus Spring compatibility extensions provide resolution for the @SpringBootApplication
annotation nor the SpringApplication
class referenced in this class.
So, what gives? We didn’t make any code changes whatsoever, yet those classes seem to resolve just fine in Quarkus.
You’ll notice a peculiar comment in both pom.xml
(for Maven)/build-quarkus.gradle
(for Gradle), right above the dependency declaration for the dependency org.springframework.boot:spring-boot-autoconfigure
:
This dependency is a hack for TodoApplication.java, which isn't required for Quarkus. Point of demo is to NOT have any code changes.
This is the key to this part of the trick. This dependency allows both Spring Boot and Quarkus to resolve these classes at build time. The dependency is declared optional
in Maven/compileOnly
in Gradle, meaning it will never be included in the application binary the Quarkus build produces. It will be included in the binary the Spring Boot build produces because all of the other spring-boot-starter-*
dependencies also depend on it, so it's included transitively.
Wrap Up
You saw in this post how to take an existing Spring Boot application and run it on Quarkus without making a single change to the code. Was the method magic or madness? Maybe a little bit of both? It's up to you to decide for yourself.
This post showed one way to migrate an application that is more than “Hello World” from Spring Boot to Quarkus with a hard requirement of not changing a single line of source code. It isn’t at all intended to represent the only potential migration path. Furthermore, there may be times when an application uses some library or API where there isn’t a Quarkus equivalent. In those cases, there may be code changes or some refactoring necessary.
A big thanks to Eric Murphy. He is the original author of the application and came up with the magic trick idea.
References
All source code for this post can be found here. The main
branch contains the Maven version, and the gradle
branch contains the Gradle version. The application source code is the same on both branches.