Guides

Getting started with Quarkus for Spring developers

As a Spring developer, you have heard more and more about Quarkus: its pros and cons, its fast growth etc. So, you decided to adopt/try Quarkus for your (next) project(s) and are wondering where to go next and what you need to pay attention to when moving from Spring to Quarkus.

This guide tries to address this exact concern. In the following, we will present you some main points you should be aware of when starting to develop with Quarkus, along with some useful sources.

  1. Quarkus is a fairly new Java toolkit. Thus, it is very well documented. It also provides a set of well-written technical guides that are a good starting point to get in touch and make the first steps with Quarkus. See here. It is an Open Source project licensed under the Apache License version 2.0. The source code is hosted in GitHub. If you have any questions or concerns, don’t hesitate to reach out to the Quarkus community.

  2. Same as Spring Initializr, you can go to code.quarkus.io to create a new application. Also, check out our Template Quarkus Guide to see our recommendations on certain topics.

  3. In Spring stack, we recommend structuring your application into multiple modules, known as our classic structure. Moving to Quarkus and the world of cloud-native microservices, where we build smaller applications compared to monoliths, we recommend keeping everything top-level and simple. Therefore, we propose the modern structure as a better fit.

  4. Quarkus focuses not only on delivering top features, but also on the developer experience. The Quarkus’s Live Coding feature automatically detects changes made to Java files, application configuration, static resources, or even classpath dependency changes and recompiles and redeploys the changes. As that, it solves the problem of traditional Java development workflow, hence improves productivity.

        Write Code → Compile → Deploy → Test Changes/ Refresh Browser/ etc → Repeat (traditional)
        Write Code → Test Changes/ Refresh Browser/ etc → Repeat (Quarkus)

    You can use this feature out of the box without any extra setup by running:

        mvn compile quarkus:dev

    Another highlight feature to speed up developing is the Quarkus’s Dev Mode with Dev Services, which can automatically provision unconfigured services in development and test mode. This means that if you include an extension and don’t configure it, Quarkus will automatically start the relevant service and wire up your application to use it, therefore saving you a lot of time setting up those services manually. In production mode, where the real configuration is provided, Dev Services will be disabled automatically.

    Additionally, you can access the Dev UI at \q\dev in Dev Mode to browse endpoints offered by various extensions, conceptually similar to what a Spring Boot actuator might provide.

  5. Quarkus is made of a small core on which hundreds of extensions rely. In fact, the power of Quarkus is its extension mechanism. Think of these extensions as your project dependencies. You can add it per dependency manager such as maven or gradle.

    mvn quarkus:list-extensions
    mvn quarkus:add-extension -Dextensions="groupId:artifactId"
    (or add it manually to pom.xml)
    # or
    gradle list-extensions
    (add dependency to build.gradle)

    Like Spring Boot, Quarkus also has a vast ecosystem of extensions with commonly-used technologies.

    Table 57. Example of common Quarkus extensions and the Spring Boot Starters with similar functionality (book: Quarkus for Spring Developer)
    Quarkus extension Spring Boot Starter

    quarkus-resteasy-jackson

    spring-boot-starter-web

    spring-boot-starter-webflux

    quarkus-resteasy-reactive-jackson

    spring-boot-starter-web

    spring-boot-starter-webflux

    quarkus-hibernate-orm-panache

    spring-boot-starter-data-jpa

    quarkus-hibernate-orm-rest-datapanache

    spring-boot-starter-data-rest

    quarkus-hibernate-reactive-panache

    spring-boot-starter-data-r2dbc

    quarkus-mongodb-panache

    spring-boot-starter-data-mongodb

    spring-boot-starter-data-mongodb-reactive

    quarkus-hibernate-validator

    spring-boot-starter-validation

    quarkus-qpid-jms

    spring-boot-starter-activemq

    quarkus-artemis-jms

    spring-boot-starter-artemis

    quarkus-cache

    spring-boot-starter-cache

    quarkus-redis-client

    spring-boot-starter-data-redis

    spring-boot-starter-data-redis-reactive

    quarkus-mailer

    spring-boot-starter-mail

    quarkus-quartz

    spring-boot-starter-quartz

    quarkus-oidc

    spring-boot-starter-oauth2-resource-server

    quarkus-oidc-client

    spring-boot-starter-oauth2-client

    quarkus-smallrye-jwt

    spring-boot-starter-security

    A full list of all Quarkus extensions can be found here. Furthermore, you can check out the community extensions hosted by Quarkiverse Hub. Quarkus has some extensions for Spring API as well, which is helpful when migrating from Spring to Quarkus.

    Besides extensions, which are officially maintained by Quarkus team, Quarkus allows adding external libraries too. While extensions can be integrated seamlessly into Quarkus, as they can be processed at build time and be built in native mode with GraalVM, external dependencies might not work out of the box with native compilation. If that is the case, you have to recompile them with the right GraalVM configuration to make them work.

  6. Quarkus' design accounted for native compilation by default. A Quarkus native executable starts much faster and utilizes far less memory than a traditional JVM (see our performace comparision between Spring and Quarkus). To get familiar with building native executable, configuring and running it, please check out our Native Image Guide. Be sure to test your code in both JVM and native mode.

  7. Both Quarkus and Spring include testing frameworks based on JUnit and Mockito. Thus, by design, Quarkus enables test-driven development by detecting affected tests as changes are made and automatically reruns them in background. As that, it gives developer instant feedback, hence improves productivity. To use continuous testing, execute the following command:

    mvn quarkus:dev
  8. For the sake of performance optimization, Quarkus avoids reflection as much as possible, favoring static class binding instead. When building a native executable, it analyzes the call tree and removes all the classes/methods/fields that are not used directly. As a consequence, the elements used via reflection are not part of the call tree so they are dead code eliminated (if not called directly in other cases).

    A common example is the JSON library, which typically use reflection to serialize the objects to JSON. If you use them out of the box, you might encounter some errors in native mode. So, be sure to register the elements for reflection explicitly. A How-to is provided by Quarkus Registering For Reflection with practical program snippets.

A very good read on the topic is the e-book Quarkus for Spring Developers by Red Hat. Another good source for direct hands-on coding tutorial is Katacoda Quarkus for Spring Boot Developers

Configuration

Quarkus provides a comprehensive guide on configuration here.

External Application Configuration
Database Configuration

In Quarkus, Hibernate is provided by the quarkus-hibernate-orm extension. Ensure the extension is added to your pom.xml as follows:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm</artifactId>
</dependency>

Additionally, you have to add the respective JDBC driver extension to your pom.xml. There are different drivers for different database types. See Quarkus Hibernate guide.

Database System and Access

You need to configure which database type you want to use, as well as the location and credentials to access it. The defaults are configured in application.properties. The file should therefore contain the properties as in the given example:

quarkus.datasource.jdbc.url=jdbc:postgresql://database.enterprise.com/app
quarkus.datasource.username=appuser01
quarkus.datasource.password=************
quarkus.datasource.db-kind=postgresql

# drop and create the database at startup (use only for local development)
quarkus.hibernate-orm.database.generation=drop-and-create
Database Logging

Add the following properties to application.properties to enable logging of database queries for debugging purposes.

quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.log.format-sql=true

#Logs SQL bind parameters. Setting it to true is obviously not recommended in production.
quarkus.hibernate-orm.log.bind-parameters=true
Secrets and environment specific configurations
Environment variables

There are also some libraries to make Jasypt work with Quarkus, such as Camel Quarkus Jasypt. Unfortunately, this feature only works in JVM mode and not in native mode.

Quarkus supports many credential providers with official extensions, such as HashiCorp Vault.

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-vault</artifactId>
</dependency>

Quarkus reads configuration values from several locations, ordered by a certain priority. An overview of these can be found at the official Quarkus config guide.

Environment variables have a higher ordinal number and are therefore higher prioritized than e.g. the application.properties file. So instead of storing secrets in plain text in the configuration files, it is better to use environment variables for critical values to configure the application.

Environment variables also have the advantage that they can be easily integrated into a containerized environment. When using Kubernetes, the secrets can be stored as Kubernetes secret and then passed to the containers as an environment variable.

Custom config sources

Quarkus provides the possability to add custom config sources, which can be used to retrieve configuration values from custom locations. For a description of this feature, see the corresponding Quarkus guide.

Config interceptors

Quarkus also allows with the concept of interceptors to hook into the resolution of configuration values. This can be useful when configuration values are encrypted or need to be extracted. To do this, you have to implement a ConfigSourceInterceptor.

public class CustomConfigInterceptor implements ConfigSourceInterceptor {

  @Override
  public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {

    ConfigValue configValue = context.proceed(name);
    if (name.equals("config-value-to-resolve")) {
      configValue = new ConfigValue.ConfigValueBuilder()
          .withName(name)
          .withValue(resolveConfigurationValue(name))
          .build();
    }

    return configValue;
  }

  private String resolveConfigurationValue(String name) {
    ...
  }
}

To use the Interceptor, you must register it. To do this, create a file io.smallrye.config.ConfigSourceInterceptor in the folder src/main/resources/META-INF/services and register the interceptor register the interceptor by writing the fully qualified class name to this file.

Credential encryption

As for Spring, there are also some libraries that let Jasypt work with Quarkus such as Camel Quarkus Jasypt. Unfortunately, this feature only works in JVM mode and not in native mode, so it is not a suitable approach.

If you want to store usernames or passwords in encrypted form or retrieve them from a custom store, you can use a custom CredentialsProvider for this purpose. Consider the use case where you want to store your database credentials in encrypted form rather than in plain text. Then you can implement a credentials provider as follows:

@ApplicationScoped
@Unremovable
public class DatabaseCredentialsProvider implements CredentialsProvider {

  @Override
  public Map<String, String> getCredentials(String credentialsProviderName) {

    Map<String, String> properties = new HashMap<>();
    properties.put(USER_PROPERTY_NAME, decryptUsername());
    properties.put(PASSWORD_PROPERTY_NAME, decryptPassword());
    return properties;
  }
}

In the application.properties file you need to set quarkus.datasource.credentials-provider=custom. For more information about the credentials provider, see the official Quarkus guide.

HashiCorp Vault

For centralized management of secrets and other critical configuration values, you can use HashiCorp Vault as external management tool.

For detailed instructions on how to integrate Vault into your Quarkus application, see the official Quarkus guide.

Quarkus template

Quarkus Code Generator is provides many alternative technologies and libraries that can be integrated into a project. Detailed guides on multiple topics can be found here.

Due to the large selection, getting started can be difficult for developers. In this guide we aim to provide a general suggestion on basic frameworks, libraries, and technologies to make it easy for developers to begin with.

With that said, please take this as a recommendation and not as a compulsion. Depending on your project requirements, you might have to use another stack compared to what is listed below.

If you are new to Quarkus, consider checking out their getting started guide to get an overview of how to create, run, test, as well as package a Quarkus application. Another recommended source to get started is the Katacoda tutorials.

Basic templates
  1. simple REST API (go to code.quarkus.io)

  2. simple REST API with monitoring (go to code.quarkus.io)

Table 58. Topic-based suggested implementation
Topic Detail Suggested implementation Note

runtime

servlet-container

Undertow

component management

dependency injection

ArC

ArC is based on JSR 365. It also provides interceptors that can be used to implement the same functionality as AOP provides

configuration

SmallRye Config

SmallRye Config is an implementation of Eclipse MicroProfile Config. It also supports YAML configuration files

persistence

OR-mapper

Hibernate ORM, Spring Data JPA

Hibernate ORM is the de facto standard JPA implementation and works perfectly in Quarkus. Quarkus also provides a compatibility layer for Spring Data JPA repositories in the form of the spring-data-jpa extension.

batch

Quarkus JBeret Extension is a non-official extension, which is hosted in the Quarkiverse Hub. It is an implementation of JSR 352.

service

REST services

RESTEasy

RESTEasy is an portable implementation of the new JCP specification JAX-RS JSR-311. It can be documented via Swagger OpenAPI.

async messaging

SmallRye Reactive Messaging, Vert.x EventBus

SmallRye Reactive Messaging is an implementation of the Eclipse MicroProfile Reactive Messaging specification 1.0. You can also utilize SmallRye Reactive Messaging in your Quarkus application to interact with Apache Kafka.

marshalling

RESTEasy Jackson, RESTEasy JSON-B, RESTEasy JAXB, RESTEasy Multipart

cloud

kubernetes

Kubernetes

deployment

Minikube, k3d

Minikube is quite popular when a Kubernetes cluster is needed for development purposes. Quarkus supports this with the quarkus-minikube extension.

logging

framework

JBoss Log Manager and the JBoss Logging facade

Internally, Quarkus uses JBoss Log Manager and the JBoss Logging facade. Logs from other supported Logging API (JBoss Logging, SLF4J, Apache Commons Logging) will be merged.

validation

framework

Hibernate Validator/Bean Validation (JSR 380)

security

authentication & authorization

JWT authentication

Quarkus supports various security mechanisms. Depending on your protocol, identity provider you can choose the necessary extensions such as quarkus-oidc quarkus-smallrye-jwt quarkus-elytron-security-oauth2.

monitoring

framework

Micrometer Metrics, SmallRye Metrics

SmallRye Metrics is an implementation of the MicroProfile Metrics specification. Quarkus also offers various extensions to customize the metrics.

health

SmallRye Health

SmallRye Health is an implementation of the MicroProfile Health specification.

fault tolerance

SmallRye Fault Tolerance

SmallRye Fault Tolerance is an implementation of the MicroProfile Fault Tolerance specification.

Building a native image

Quarkus provides the ability to create a native executable of the application called native image. Unlike other Java based deployments, a native image will only run on the architecture and operating system it is compiled for. Also, no JVM is needed to run the native-image. This improves the startup time, performance, and efficiency. A distribution of GraalVM is needed. You can find the differences between the available distributions here.

To build your quarkus app as a native-image, you have two options that are described in the following sections.

Build a native executable with GraalVM

To build a Quarkus application, you can install GraalVM locally on your machine, as described below. Therefore, read the basic Quarkus application chapter, or clone the example project provided by devonfw. Follow this chapter from the Quarkus Guide for building a native executable.

Installing GraalVM

A native image can be created locally or through a container environment. To create a native image locally, an installed and configured version of GraalVM is needed. You can follow the installation guide from Quarkus or the guide provided by GraalVM for this.

Build a native executable with GraalVM through container environment

In order to make the build of native images more portable, you can also use your container environment and run the GraalVM inside a container (typically Docker). You can simply install Docker with your devonfw-ide distribution, just follow this description Docker with devonfw-ide. Follow this chapter to build a native Linux image through container runtime.

Configuring the native executable

A list of all configuration properties for a native image can be found here.

Bean mapping with Quarkus

This guide will show bean-mapping, in particular for a Quarkus application. We recommend using MapStruct with a Quarkus application because the other bean-mapper frameworks use Java reflections. They are not supported in GraalVm right now and cause problems when building native applications. MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. The mapping code will be generated at compile-time and uses plain method invocations and is thus fast, type-safe, and easy to understand. MapStruct has to be configured to not use Java reflections, which will be shown in this guide.

You can find the official MapStruct reference guide and a general introduction to MapStruct from Baeldung.

MapStruct Dependency

To get access to MapStruct, we have to add the dependency to our POM.xml:

<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.4.2.Final</version>
  <scope>provided</scope>
</dependency>

MapStruct provides an annotation processor that also has to be added to the POM.xml

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>3.8.1</version>
	<configuration>
		<source>1.8</source>
		<target>1.8</target>
		<annotationProcessorPaths>
			<path>
				<groupId>org.mapstruct</groupId>
				<artifactId>mapstruct-processor</artifactId>
				<version>1.4.2.Final</version>
			</path>
		</path>
		</annotationProcessorPaths>
	</configuration>
</plugin>

MapStruct takes advantage of generated getters, setters, and constructors from the Lombok library, follow this Lombok with Mapstruct guide to get Lombok with Mapstruct working.

MapStruct Configuration

We already discussed the benefits of dependency injection. MapStruct supports CDI with EJB, spring, and jsr330. The default retrieving method for a mapper is a factory that uses reflections, which should be avoided. The component model should be set to CDI, as this will allow us to easily inject the generated mapper implementation. The component model can be configured in multiple ways.

Simple Configuration

Add the attribute componentModel to the @Mapper annotation in the mapper interface.

@Mapper(compnentModel = "cdi")
public interface ProductMapper{
  ...
}
MapperConfig Configuration

Create a shared configuration that can be used for multiple mappers. Implement an interface and use the annotation @MapperConfig for the class. You can define all configurations in this interface and pass the generated MapperConfig.class with the config attribute to the mapper. The MapperConfig also defines the InjectionStrategy and MappingInheritaceStrategy, both of which will be explained later. A list of all configurations can be found here.

@MapperConfig(
  compnentModel = "cdi",
  mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG
  injectionStrategy =InjectionStrategy.CONSTRUCTOR
)
public interface MapperConfig{
}
@Mapper( config = MapperConfig.class )
public interface ProductMapper{
  ...
}

Any attributes not given via @Mapper will be inherited from the shared configuration MapperConfig.class.

Configuration via annotation processor options

The MapStruct code generator can be configured using annotation processor options. You can pass the options to the compiler while invoking javac directly, or add the parameters to the maven configuration in the POM.xml

We also use the constructor injection strategy to avoid field injections and potential reflections. This will also simplify our tests.

The option to pass the parameter to the annotation processor in the POM.xml is used and can be inspected in our quarkus reference project.

A list of all annotation processor options can be found here.

Basic Bean-Mapper Usage

To use the mapper, we have to implement the mapper interface and the function prototypes with a @Mapper annotation.

@Mapper
public interface ProductMapper {

  ProductDto map(ProductEntity model);

  ProductEntity create(NewProductDto dto);
}

The MapStruct annotation processor will generate the implementation for us under /target/generated-sources/, we just need to tell it that we would like to have a method that accepts a ProductEntity entity and returns a new ProductDto DTO.

The generated mapper implementation will be marked with the @ApplicationScoped annotation and can thus be injected into fields, constructor arguments, etc. using the @Inject annotation:

public class ProductRestService{

  @Inject
  ProductMapper mapper;
}

That is the basic usage of a Mapstruct mapper. In the next chapter, we’ll go into a bit more detail and show some more configurations.

Advanced Bean-Mapper Usage

Let´s assume that our Product entity and the ProductDto have some differently named properties that should be mapped. Add a mapping annotation to map the property type from Product to kind from ProductDto. We define the source name of the property and the target name.

@Mapper
public interface ProductMapper {
  @Mapping(target = "kind", source = "type")
  ProductDto map(ProductEntity entity);

  @InheritInverseConfiguration(name = "map" )
  ProductEntity create(ProductDto dto);
}

For bi-directional mappings, we can indicate that a method shall inherit the inverse configuration of the corresponding method with the @InheritInverseConfiguration. You can omit the name parameter if the result type of method A is the same as the single-source type of method B and if the single-source type of A is the same as the result type of B. If multiple apply, the attribute name is needed. Specific mappings from the inverse method can (optionally) be overridden, ignored, or set to constants or expressions.

The mappingInheritanceStrategy can be defined as showed in MapStruct Configuration. The existing options can be found here.

A mapped attribute does not always have the same type in the source and target objects. For instance, an attribute may be of type int in the source bean but of type Long in the target bean.

Another example are references to other objects which should be mapped to the corresponding types in the target model. E.g. the class ShoppingCart might have a property content of the type Product which needs to be converted into a ProductDto object when mapping a ShoppingCart object to ShoppingCartDto. For these cases, it’s useful to understand how Mapstruct converts the data types and the object references.

Also, the Chapter for nested bean mappings will help to configure MapStruct to map arbitrarily deep object graphs.

You can study running MapStruct implementation examples given by MapStruct or in our Quarkus reference project

Last updated 2023-11-20 10:37:01 UTC