MapStruct and Quarkus - a match made in heaven?

This year is nearly over, but it was started with something new that came up in the Java world: Quarkus. You may already have heard about it, if not, don’t worry, I will quickly summarize what it is.

Additionally to this post you can also find a working example in our examples repository.

Quarkus

Supersonic Subatomic Java

A Kubernetes Native Java stack tailored for GraalVM & OpenJDK HotSpot, crafted from the best of breed Java libraries and standards

That’s the description on quarkus.io, but what does this mean?

With Quarkus you can build microservices or other Java applications using a stack of already existing components like Eclipse Vert.x, Hibernate or Eclipse MicroProfile. It has a container-first philosophy which means that Quarkus puts all these frameworks together in a way that the resulting application works very well in container environments like Kubernetes or OpenShift.

Read this article if you want to know more about Quarkus itself.

And what is so special when using it with MapStruct?

The special thing about Quarkus is that it not only allows to run applications on the JVM, but also as native binaries via GraalVM. This provides ahead-of-time compilation that creates a native image of your application (native system-dependent machine code and not bytecode). Thus you don’t need to have an own JVM installation to run the code, similar to a compiled C++ or Go application.
This allows having the advantages of native applications also for Java-based apps like a much faster startup time, less memory usage and a much smaller image (25+ MB for a REST application instead of easily more than 100 MB for the JVM and required libraries).

Using a native image is not possible for all kinds of applications, especially when reflection is used the GraalVM compiler needs some assistance by the developer in order to create the native image. That means you have to provide reflection metadata to the compiler which makes the development more complicated.

And thats exactly the point where MapStruct jumps in as a reflection-free mapping library!

How to integrate MapStruct in a Quarkus application

So let us see how well MapStruct plays together with Quarkus!

I made an example application that will show you how you can integrate MapStruct in your Quarkus application. You will find the complete demo application in our examples repository.

The example should be a simple REST application just having a /person endpoint returning information about a person. Obviously, the internally used classes should not be exposed, but a DTO will be created and filled with information from a MapStruct mapper.

Create a new Quarkus application

After reading the Getting Started on the Quarkus pages it was really easy to figure out how to create a new application. Just run the following command:

mvn io.quarkus:quarkus-maven-plugin:1.0.1.Final:create \
  -DprojectGroupId=org.mapstruct.examples.quarkus \
  -DprojectArtifactId=mapstruct-examples-quarkus \
  -DclassName="org.mapstruct.example.quarkus.PersonResource" \
  -Dpath="/person" \
  -Dextensions="resteasy-jsonb"

This will setup a new Quarkus application having a REST endpoint /person defined in a PersonResource. We want to expose the JSON structure automatically, so we need the resteasy-jsonb extension that will allow us to marshall our object to JSON (like you probably know it from Spring REST or similar frameworks).

Adding MapStruct

You just need to add MapStruct in your projects POM:

<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.3.1.Final</version>
</dependency>
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct-processor</artifactId>
  <version>1.3.1.Final</version>
  <scope>provided</scope>
</dependency>

Side note: In case you use for example Lombok pay attention that you must define the Lombok dependency before the one for the MapStruct processor.

Person service

This example application will have a Person POJO holding information about the person and a PersonService that will return the person.

So we have to create the following two classes:

public class Person {
  private String firstname;
  private String lastname;
  
  // all-args constructor, getters, setters
}
@ApplicationScoped
public class PersonService {
  public Person loadPerson() {
    return new Person("Bob", "Miller");
  }
}

What is the first thing you notice?

Okay… the service is very boring. It always returns the same object. But that’s not what I meant, it’s just an example. Most likely, your application will fetch the information from an external system.

What I meant is the @ApplicationScoped annotation that was used. If you are a Spring developer you might not know it, but this is a regular Jakarta EE annotation that belongs to CDI and marks this class as (more or less) an application-wide singleton that is “injectable”. Similar to @Component or @Service from Spring.

Most of the commonly used frameworks are using reflection for the DI and as mentioned before reflection doesn’t play well with native images. But the Quarkus team did a great job and provided a CDI implementation that can be used together with native images - so that’s then most likely the way to go when you need dependency injection and build a Quarkus application!

DTO and mapper

We don’t like to expose internal objects, thus we create a new PersonDto that matches our API.

public class PersonDto {
  private String firstname;
  private String surname;
  
  // getters, setters
}

So now, we have a Person and PersonDto that both have a similar structure. We are able to retrieve a Person, but want to return a PersonDto, so that’s the time where we finally add our MapStruct mapper!

@Mapper(componentModel = "cdi")
public interface PersonMapper {
    @Mapping(target = "surname", source = "lastname")
    PersonDto toResource(Person person);
}

The MapStruct annotation processor will generate the implementation for us, we just need to tell it that we would like to have a method that accepts a Person and returns a new PersonDto. A little trap: Our DTO has the property surname, so we add a mapping annotation to map lastname from Person to surname from PersonDto.

Very important: The component model should be set to CDI, as this will allow us to easily inject the generated mapper implementation.

Side note: As you need to use CDI for all mappers it’s recommended to define a @MapperConfig and refer to it in all your mappers, this is the way I did it in the example on GitHub. But for simplicity I skipped it here.

Endpoint

As a last step we need to create our endpoint that will get the correct Person and finally returns the PersonDto.

The PersonResource was already generated by Quarkus providing a /person endpoint that just returns a static text. We will remove this operation and exchange it with a new one that returns our person:

@Path("/person")
public class PersonResource {
 
  @Inject
  PersonService personService;
 
  @Inject
  PersonMapper personMapper;
 
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public PersonDto loadPerson() {
    return personMapper.toResource( personService.loadPerson() );
  }
}

To be able to access our PersonService and PersonMapper we will inject both using CDI.

The loadPerson() method will then just call our service to load the person, throw it into our mapper and return the DTO.

That’s it!

Start the application

If you would like to run the application you just need to hit mvn compile quarkus:dev. This will compile everything (which includes mapper generation) and starts a local server running the application.

After starting the application you should see a log output similar to the following one:

Quarkus 1.0.1.Final started in 1.448s. Listening on: http://0.0.0.0:8080
Profile dev activated. Live Coding activated.
Installed features: [cdi, resteasy, resteasy-jsonb]

You can now open http://localhost:8080/person in your browser and should see a JSON representating our person.

Build the native image

Okay, starting the application took nearly one and a half second. Very slow, isn’t it? That means every time the app will start you need to waste 1.5 seconds of your lifetime… okay, just kidding. That’s already really fast! But okay, it is also a really small application. Nevertheless, it should be mentioned that Quarkus also has a few optimizations to start the server when using the regular JVM really fast. But yeah, we can do it even faster. So let us build a native image!

Following the guide on the Quarkus page, you can build the native image just using one command:

mvn package -Pnative

(In case you get an error that tests fail remove those or update them, I skipped this here as well)

As a prerequisite you need to have the GraalVM installed correctly. In case you don’t want or can install it on your machine take a look at the multi-stage Docker build in the previously mentioned guide.

The command will produce an executable application in the target folder (it ends with -runner) that you can just start.

You will notice that running the native compilation will take a while as it will not just compile your application as usual, it will afterwards create the native image. This process takes some time, but you will save this time afterwards in production when you need to (re)start your application plenty of times.

The output after starting the app should look like this:

mapstruct-examples-quarkus 1.0-SNAPSHOT (running on Quarkus 1.0.1.Final) started in 0.006s. Listening on: http://0.0.0.0:8080
Profile prod activated. 
Installed features: [cdi, resteasy, resteasy-jsonb]

I will repeat: started in 0.006s.

This is incredibly fast!

Summing up

This example shows how easy it is to integrate MapStruct in a Quarkus application. This is a really huge plus-point for using MapStruct as the mapping framework in a Quarkus application compared to most of the other mapping frameworks that are using reflection.

Therefore generating the mapper implementation does not only lead to be a really fast mapping framework, but also to be a “native image” ready one!

So yes: MapStruct and Quarkus is definitely a match made in heaven!

The important point is to use CDI. The default component model (that’s where you retrieve the mapper with Mappers.getMapper(PersonMapper.class)) will not work out-of-the-box as this is the only part in MapStruct where reflection is used. If you still prefer this way you have to provide an own reflection configuration. Maybe in the future MapStruct will also support this out-of-the box. But with version 1.3.1.Final it is not yet supported.

One small disadvantage still left

In case you are an experienced MapStruct user you might have noticed that the way how I added MapStruct is a bit different to the way it is written in our reference guide.

The Quarkus development mode provides a hot-reload of your application, unfortunately it seems that the annotation processor will not be triggered again when you define the MapStruct processor within the annotation-processor path of the maven-compiler-plugin and you have to restart the application to see changes on the mappings.

Adding our processor as a regular dependency will ensure that the processor will be used in the dev-mode in case you make changes to a @Mapper annotated class. Unfortunately, one point is still not working out-of-the-box: When you change classes not annotated with @Mapper that would change the mapper implementation (like the DTO) the mapper will not be regenerated. I guess that Quarkus is not able to know that the changed DTO has a side effect on the corresponding mapper and as this one will not be regenerated the annotation processor will not update the mapper implementation (see Quarkus issue #1502).

comments powered by Disqus