The last couple of years I have worked and experimented a lot with injection frameworks. New frameworks like Micronaut and Quarkus in my personal environment, while professionally working primarily with Spring Boot, and Jakarta EE on JBoss EAP. Since we (my almighty dev-partner-in-crime Roger Goossens and me) have a feeling that Micronaut and Quarkus have become more stable, we are going to find out what it takes to migrate from the popular Spring Boot to another injection framework. In this post I will show you how to migrate to Quarkus. A while back in this post, Roger has shown you to migrate to Micronaut. In a followup we will compare the two and tell you what is easy to migrate and what is lacking.
For this experiment Roger has created a simple Spring Boot application with a Rest interface on top of MongoDB. With Spring Boot this is a simple exercise (work of art). Now let’s see what it takes to migrate to Quarkus and create a native image!
First a list of what we need to migrate:
- pom.xml
- Annotations
- MongoDB Implematation
- Rest Configuration
- Application Starter
So, all in all, we need to look at the entire application!
Dependencies
Alright, first we need to purge anything that is called spring and then in essence replace it with quarkus. At the moment of writing the latest and greatest version of Quarkus is 1.4.2.Final so we will be using that one. Out with the old, in with the new!
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus-plugin.version>1.12.2.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.12.2.Final</quarkus.platform.version>
<surefire-plugin.version>2.22.1</surefire-plugin.version>
</properties>
<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>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
...
As you can see in the above example, I have added the Quarkus dependencies. The big difference is that Spring uses a parent construction and Quarkus uses the BOM (Bill of Material) construction.
In order to use some of the quarkus plugins we have to add the following plugin:
<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>
With this we can start the application and do all kinds of cool stuff like hot deploy!
Now we have updated the dependencies, the next step is removing the Spring stuff from the code. The first thing to head over to is the Application.java class, or whatever the main class is called in your application. Now, normally when I want to run or debug my spring-boot application from my IDE I would use the main class. With Quarkus you don’t have a main class and you need to start the application a bit different. For this you can use the quarkus-maven-plugin.
Simply run “mvn quarkus:dev” and quarkus will be started. The log will state that you can debug it at port 5005, so all you have to do is create a remote debugging session from your IDE with port 5005 as the target and you’re good to go. If you make a change, you will notice that quarkus:dev will act as a hot deploy and as soon as you save something, it will recompile your code.
The second thing you need to replace are the annotations. Spring has its own set of annotations while Quarkus uses the Jakarta EE annotations. Although it is not fully compliant with Jakarta, you can use all the annotations for injection. That means that @Autowired, @Component etc have to be replaced with their corresponding annotations like @Inject and @Named.
Now that we have the basics covered, let’s look at some actual implementation stuff. First off, is the REST controller. In the post https://terra10.nl/blog/micronaut-vs-quarkus-part-i-the-spring-boot-chronicles/ you can see how Spring handles this.
Quarkus uses the RestEasy (https://resteasy.github.io/) implementation for it’s restful interfaces.
package org.acme.mongodb;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import org.acme.mongodb.model.Beer;
@Path("/beers/1.0")
@Consumes("application/json")
@Produces("application/json")
public class BeerController {
@GET()
public List<Beer> getAllBeers() {
return new ArrayList<>();
}
}
In this I have specified that BeerController is a rest interface which is hosted at /beers/1.0/ and has a single get operation that returns a list of all the known beers.
Quarkus uses the RestEasy library for the actual work that is happening in the background. Simply add the dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
RestEasy is quite easy to implement. The following code snippet shows you how to create a RestFull interface with just a few methods and annotations.
@Path("/beers/1.0")
@Consumes("application/json")
@Produces("application/json")
public class BeerController {
private final BeerRepository beerRepository;
public BeerController(BeerRepository beerRepository) {
this.beerRepository = beerRepository;
}
@GET()
public List<Beer> getAllBeers() {
return beerRepository.listAll();
}
@GET
@Path("/{beerName}")
public Beer getBeerByName(@PathParam("beerName") String name) {
return beerRepository.findByName(name);
}
@POST
public Response saveOrUpdateBeer(Beer beer) {
beerRepository.persistOrUpdate(beer);
return Response.ok("Beer added successfully").build();
}
@DELETE
@Path(value = "/{beerName}")
public Response deleteBeer(@PathParam("beerName") String name) {
beerRepository.delete(beerRepository.findByName(name));
return Response.noContent().build();
}
}
As you can see, RestEasy is a nice and clean way of defining your rest interfaces, by naming conventions conform to the HTTP rest protocol. From a programming perspective it is quite the same as the Spring-boot restController implementation. Just like Spring-boot, Quarkus will look for the RestController annotation and automatically pick up the rest implementation and host it for you. The main differences are the annotations. So migrating your code won’t be a big hassle.
Last but not least, the database implementation. Just like SpringBoot, Quarkus has starters, only in Quarkus they are called extensions. For mongodb you have two options, the mongodb client and mongodb with panache. Both offer an easy to configure, property based, mongodb client. However panache takes it one step further. It offers hibernate ORM styled functionality. This means that you can create a pojo styled entity object which you can use to interact with the database. Besides the entity you can also create a repository class which you can use to query the database.
@ApplicationScoped
public class BeerRepository implements PanacheMongoRepository<Beer> {
public Beer findByName(String name){
return find("name", name).firstResult();
}
}
As mentioned the mongodb-panache extension will handle the connection to the database and it will pick up the repository bean. Meaning no coding is required to set up the connection to the database. Creating a repository can be done by implementing a small class. Just implement the above code and you are good to go. Just don’t forget to add the following properties to the application.properties file:
# configure the MongoDB client for a replica set of two nodes
quarkus.mongodb.connection-string = mongodb://localhost:27017
# mandatory if you don't specify the name of the database using @MongoEntity
quarkus.mongodb.database = terraxbeer
For those who are wondering, the Beer class looks just like a pojo, with the extra that it implements a PanacheMongoEntity class:
@MongoEntity(collection="Beers")
public class Beer extends PanacheMongoEntity {
public Beer() {
}
private String name;
private Brewery brewery;
Note the annotation is not required: in this case I used it to specify the database that is used for the entity. In my case the “Beers” database. This could also be done with a property: “quarkus.mongodb.database”.
That’s all! As seen in the RestController, the BeerRepository class is injected into the controller. The only thing you need to do to query the database is calling some java methods like:
beerRepository.listAll();
beerRepository.persistOrUpdate(beer);
beerRepository.delete(beerRepository.findByName(name));
Now the final part, my personal favourite, the Unit Testing. In my opinion something that can make or break a framework. Nothing is as annoying as when you spend 5 minutes of writing beautiful code, and have to spend the rest of the day getting your tests working. Luckily Quarkus got you covered!
Quarkus comes with a lot of tooling that is well known in the Java world. This is also applicable for the Unit Testing framework. Out of the box it comes with Mockito (for your stubs), RestAssured (for your rest interfaces) etc.. If you have already used those frameworks, setting up a test should be easy. First, add the annotation “QuarkusTest” to your test class, this will take care of all the injection magic ! Next, add a mock for your mongo repository. The “InjectMock” annotation will handle that for you. It injects a mockito mock that you can easily use to set your expectations.
Last but not least, trigger the API with restAssured, by implementing the test logic, given, when, then. In my case, when calling “beers/1.0/panache” I should get a 200 OK and a body that contains the expected json object.
@QuarkusTest
public class BeerControllerTest {
@InjectMock
BeerRepository beerRepository;
@Test
public void testFindBeerByName() {
Beer beer = new Beer();
beer.setName("panache");
Mockito.when(beerRepository.findByName("banana")).thenReturn(beer);
given()
.when().get("beers/1.0/panache")
.then()
.statusCode(200)
.body(is("{\"name\":\"panache\"}"));
}
}
That’s it. We have migrated a SpringBoot application to Quarkus, and in doing so made a comparison. I love working with SpringBoot and I see a lot of it coming back to Quarkus. In short, Quarkus copies a lot of what is good in SpringBoot and adds a ton of cool features without reinventing the wheel. Reuse of widely accepted frameworks, such as Mockito, RestEasy, JakartaEE etc. and added cool features like hot deployment and – because it is built with GraalVM in mind – native-images. For what it is worth, I hope to be working with Quarkus in my next project!
SourceCode:
https://github.com/djjanssen/blog-spring-to-quarkus-with-mongo-panache
Useful links:
https://quarkus.io/get-started/
https://resteasy.github.io/
https://github.com/rest-assured/rest-assured
https://quarkus.io/guides/mongodb-panache