From Spring Boot to Quarkus

Nicolas Fränkel - Nov 15 '20 - - Dev Community

Last week, I migrated a sample Spring application to Micronaut. This week, I did the same for Quarkus.

Common changes

Spring Boot and Micronaut both offer a parent POM. Quarkus favors using a BOM.

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

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-bom</artifactId>
      <version>1.9.2.Final</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
Enter fullscreen mode Exit fullscreen mode

Migrating the application

Quarkus provides dependencies that have the same interfaces and annotations as Spring. They map one to one. The next step is just a matter of replacing Spring's dependencies with Quarkus'.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-spring-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-spring-web</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

To launch the application requires just a single main() function, just as Micronaut:

@SpringBootApplication
class SpringToMicronautApplication

fun main(args: Array<String>) {
  runApplication<SpringToMicronautApplication>(*args)
}

fun main(args: Array<String>) {
  Quarkus.run(*args)
}
Enter fullscreen mode Exit fullscreen mode

There's a glitch on the controller side. Quarkus doesn't read the @PathVariable variable name from the bytecode. The name needs to be explicitly set in the annotation:

fun getOne(@PathVariable("id") id: Long): Optional<Person> = repo.findById(id)
Enter fullscreen mode Exit fullscreen mode

The beauty of Quarkus is that no further steps are required!

More migration steps on the data layer

Though the database driver is on the classpath, it's necessary to configure the database:

quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.driver=org.h2.Driver
quarkus.datasource.jdbc.url=jdbc:h2:mem:test
Enter fullscreen mode Exit fullscreen mode

Just as for Micronaut, Quarkus allow neither to create the schema nor to initialize the data, but has a Flyway integration:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-flyway</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Moreover, you need to set the quarkus.flyway.migrate-at-start property:

quarkus.flyway.migrate-at-start=true
Enter fullscreen mode Exit fullscreen mode

Migrating the actuator

There's no actuator per se in the Quarkus universe. But it provides integration with SmallRye, which is an implementation of MicroProfile "(but not limited to) for the Cloud". Smallrye also provides a /health endpoint. It just requires to add a dependency:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-smallrye-health</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

By default, the endpoint is available at the root. To mirror Spring's URL is a configuration knob away:

quarkus.smallrye-health.root-path=/actuator/health
Enter fullscreen mode Exit fullscreen mode

In Spring applications, the /beans endpoint queries the Spring context and return the list of beans it contains. I found no counterpart in Smallrye nor Microprofile. Of course, a CDI bean and a Spring Boot bean are different. Let's return the list of CDI beans.

In CDI, the BeanManager is the object that's able to do that. The endpoint is a simple controller.

@RestController
class BeanController(private val manager: BeanManager) {              // 1

  @GetMapping(path = ["/actuator/beans"])                             // 2
  fun getAll(): Iterable<Bean<*>> = manager.getBeans(Any::class.java) // 3
}
Enter fullscreen mode Exit fullscreen mode
  1. Inject the BeanManager
  2. Configure the endpoint URL
  3. List the beans and return them

Conclusion

Within the scope of this sample application, migrating from Spring Boot to Quarkus is straightforward. The API dependencies play a big role in that. Except for explicitly setting the parameter name, it's seamless.

I was a bit surprised there's no AOT compilation as with Micronaut. I assume GraalVM native image creation leverages some mechanism to make reflection explicit.

The real gap lies in the actuator but it's possible to implement missing endpoints manually.

The complete source code for this post can be found on Github.

To go further:

Originally published at A Java Geek on November 15th 2020

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .