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.
-
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.
-
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.
-
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.
-
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. -
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.
-
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.
-
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
-
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
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.
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
-
simple REST API (go to code.quarkus.io)
-
simple REST API with monitoring (go to code.quarkus.io)
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 is an implementation of Eclipse MicroProfile Config. It also supports YAML configuration files |
||
persistence |
OR-mapper |
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 |
|
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 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 |
|
logging |
framework |
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 |
monitoring |
framework |
SmallRye Metrics is an implementation of the MicroProfile Metrics specification. Quarkus also offers various extensions to customize the metrics. |
|
health |
SmallRye Health is an implementation of the MicroProfile Health specification. |
||
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