Quarkus vs Spring Boot: What You Need to Know

In this two-part article series, we will be comparing Quarkus vs Spring Boot taking into account aspects related to the performance and runtime statistics of a simple example application. In the next part, we will cover other key aspects such as cloud-native readiness. Get ready: this is Quarkus vs Spring Boot!

First off, in terms of product, Quarkus is an OpenSource product developed by Red Hat engineers. The enterprise version of it is Red Hat Build of Quarkus.

Spring Boot is as well an Open Source project developed by the Pivotal team which provides as well Enterprise Support for Spring Boot. Spring Boot is supported as well by Red Hat as part of Red Hat Runtimes portfolio (https://access.redhat.com/products/red-hat-runtimes)

Quarkus vs Spring Boot: project bootstrap

Where does our journey begins? from application startup obviously ! You can bootstrap your Quarkus and Spring Boot applications with a variety of tools. Let’s check the available options:

To get started with Quarkus you have several options:

  • Using the Maven and Gradle plugins which create for you a project with the default structure
  • Online (https://code.quarkus.io/) directly from the Cloud.
  • Using the Quarkus Command Line tool, since Quarkus 2.x
  • Installing the plugin for your favourite IDE (including IntelliJ, JBoss Tools for Eclipse, Visual Studio, Eclipse Che)

To get started with Spring Boot you also have a variety of choices:

  • Using the Online Initializer (https://start.spring.io/).
  • Through the powerful Spring Command Line Interface (https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/).
  • Using the plugin for your favourite IDE including IntelliJ, Visual Studio, or the most complete choice which is Spring Tools (https://spring.io/tools)

Comparing the runnable JARs

As both Spring Boot and Quarkus target a serverless, micro service environment, you can produce runnable Jar files out of your applications. We will be comparing a simple application using a REST Controller backed by a minimal Persistence layer and see what we get from each framework. The example applications I will use for this purpose are:

Upon building both applications as pure Java applications, here is the resulting application:

Quarkus Application:

11046 Dec 20 13:14 hibernate-orm-quickstart-1.0.0-SNAPSHOT.jar

The runner jar of Quarkus is a tiny one, however, is not an Uber file. The external libraries are in the target/quarkus-app/lib folder.

$ du -sh ./target/quarkus-app/lib/

31M ./target/quarkus-app/lib/

Spring Boot application consists in a single Uber Jar file:

36214416 May  1 09:56 cruddemo-0.0.1-SNAPSHOT.jar

So on a balance Spring Boot produced an all-in-one Uber jar (about 36 MB), while Quarkus packages a wrapper Runnable Jar with a set of libraries (about 31 MB in total).

Comparing start-up time

Aside from the resulting artifacts, it is way more interesting to check the start time of applications, which is a key factor for microservices applications. Let’s start comparing the boot time for both applications, packaged as Java regular applications:

The Quarkus application, launched as Java application, took about 1.7 seconds to start:

$ java -jar ./target/quarkus-app/quarkus-run.jar

2021-12-20 13:29:27,179 INFO  [io.quarkus] (main) hibernate-orm-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.5.3.Final) started in 1.764s. Listening on: http://0.0.0.0:8080
2021-12-20 13:29:27,182 INFO  [io.quarkus] (main) Profile prod activated. 
2021-12-20 13:29:27,183 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, vertx]

The Spring Boot application, on the other hand, required about 4.6 seconds to start:

2020-05-01 10:08:34.739  INFO 13417 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page template: index
2020-05-01 10:08:34.900  INFO 13417 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-01 10:08:34.902  INFO 13417 --- [           main] com.example.cruddemo.DemoApplication     : Started DemoApplication in 4.604 seconds (JVM running for 5.07)

Let’s check the memory usage of both applications.

Comparing Memory Usage

In terms of memory usage,let’s launch VisualVM to monitor the amount of Memory used by both applications.

$ java -jar ./target/quarkus-app/quarkus-run.jar

Here is the Heap size of the Quarkus application. As you can see, it is currently using about 61 MB:

Let’s now check the Spring Boot application:

$ java -jar target/cruddemo-0.0.1-SNAPSHOT.jar

The Spring Application is currently using about 172 MB, so almost 300% more than the Quarkus application:

Native executable with Quarkus and Spring Boot

One of the biggest highlights of Quarkus is its ability to create native cloud-ready applications, once you have installed GRAALVM and configured the native-image tooling. This brings amazing improvements in terms of memory usage and especially application start-up. In Quarkus you can generate a native executable from your application by using its tooling and the “native” profile:

$ mvn verify -Pnative

Once completed the build, we will launch the application:

$ target/hibernate-orm-quickstart-1.0-SNAPSHOT-runner

 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
 
2020-05-03 09:44:05,171 INFO  [io.quarkus] (main) hibernate-orm-quickstart 1.0-SNAPSHOT (powered by Quarkus 1.3.2.Final) started in 0.077s. Listening on: http://0.0.0.0:8080
2020-05-03 09:44:05,171 INFO  [io.quarkus] (main) Profile prod activated. 
2020-05-03 09:44:05,171 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy, resteasy-jsonb]

As you can see, it took just 0.077s to start the application. On the other hand,in terms of memory usage the Quarkus native application uses just a fraction of its Java counterpart: barely 44 MB:

ps -eo pid,vsz,rss,comm | grep hibernate
16376 1648624 44296 hibernate-orm-q

On the Spring Boot side, you can use Spring Native to support native compilation of Spring Boot applications using the GraalVM native-image compiler.

The core feature is the spring-graal-native-feature which is a Spring GraalVM feature for the native-image compilation process.

After adding Spring Native support to our Spring REST application, we have run it:

$ target/crud-demo

2021-12-20 18:41:11.216 DEBUG 8345 --- [           main] o.s.d.r.w.BasePathAwareHandlerMapping    : 5 mappings in org.springframework.data.rest.webmvc.BasePathAwareHandlerMapping
2021-12-20 18:41:11.223  INFO 8345 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-12-20 18:41:11.224  INFO 8345 --- [           main] com.example.data.rest.Application        : Started Application in 0.27 seconds (JVM running for 0.271)

As you can see, the application started in 0.27 seconds.

In terms of memory usage, here is the report:

ps -eo pid,vsz,rss,comm | grep crud-demo
 8345 1189628 239060 crud-demo

Overall, the Spring Boot native image has a Process Resident Size of about 239 MB.

Cloud readiness: Quarkus vs Spring Boot

Both Spring Boot and Quarkus are cloud-ready solutions, meaning that they can be deployed in a platform capable of managing containerized workloads such as Kubernates or OpenShift. Let’s check how we can build a container image of our application in both cases.

When you build your Quarkus application with any available starter, it will create for you a distinct folder named “docker” which already includes the following Dockerfiles:

src
├── main
│   ├── docker
│   │   ├── Dockerfile.jvm
│   │   ├── Dockerfile.legacy-jar
│   │   ├── Dockerfile.native
│   │   └── Dockerfile.native-distroless

Therefore, once you have packaged your application using the option quarkus.native.container-build=true:

$ mvn package -Pnative -Dquarkus.native.container-build=true

Then it’s just a matter of building and running the Docker file you want to use:

$ docker build -f src/main/docker/Dockerfile.native -t quarkus/hibernate-orm .
	
$ docker run -i --rm -p 8080:8080 quarkus/hibernate-orm

Let’s check some stats about the Quarkus application when run in a Docker container using ‘docker stats’:

CONTAINER           CPU %               MEM USAGE / LIMIT
hibernate-orm       0.09%               293.7 MiB / 30.8 GiB

Let’s see how to build a Container image of a Spring Boot application. There are several approaches to it. As Spring Boot delivers an Uber Jar file, most developers create a basic Docker file which starts from the openjdk:8-jdk-alpine (or newer) and add the Spring Application on the top of it:

FROM openjdk:8-jdk-alpine

VOLUME /tmp

EXPOSE 8080

ARG JAR_FILE=target/cruddemo-0.0.1-SNAPSHOT.jar

ADD ${JAR_FILE} cruddemo-0.0.1-SNAPSHOT.jar

ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/cruddemo-0.0.1-SNAPSHOT.jar"]

When building and running the Spring Boot CRUD Docker image, we will have the following statistics from the Docker image:

CONTAINER            CPU %               MEM USAGE / LIMIT
cruddemo             0.09%               464.1 MiB / 30.8 GiB

The overall memory usage of the Quarkus and Spring Boot images are greatly influenced by the distros on which the application layer runs, however the Quarkus one shows smaller memory usage and can be kick-started for Docker without any additional coding. It is worth mentioning, however, that since Spring Boot 2.3.0.M1, it will be possible to have in your Spring Boot projects support for building Container images just by running:

mvn spring-boot:build-image

Applications Live reload

Live reload is an essential feature for Java developers, so that they can update their application code and see the result without restarting the Java process. Both Quarkus and Spring Boot feature a live reload of your applications. Let’s compare their approach.

On the Quarkus side, the live reload feature is totally transparent when running in development mode. Every change to your code or configuration files results in a transparent live reload of your application:

2020-05-03 11:01:38,201 INFO  [io.qua.dev] (vert.x-worker-thread-0) Hot replace total time: 0.942s

It is worth noticing that Live reload can be triggered also on a remote environment, so for example on a Kubernates/OpenShift cluster, by passing the live reload URL of the remote host at start:

$ mvn quarkus:remote-dev -Dquarkus.live-reload.url=http://my-remote-host:8080

On the Spring Boot side, live reload can be instrumented in your applications by including the spring-boot-devtools in your configuration:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
</dependency>

Then, provided that you have recompiled the application code, you will be able to see changes live without restarting the Spring Boot process.

Summary

At the end of this first round, we have checked how a basic CRUD application performs when using Quarkus and Spring Boot. For the time being, Quarkus has the evident advantage of being designed from the grounds up to be cloud-native ready, using a minimal memory footprint and development joy, thanks to its built-in live reload feature. On the other side, Spring Boot applications, which produce an all-in-one Uber Jar, can also easily plugged into any JDK based distribution to produce cloud-ready applications. In the next article, we will move our comparison on another field, the core libraries/extensions which make the ecosystem of both frameworks. Continue reading the part 2: Quarkus vs Spring Boot – Part 2

How to change Quarkus default HTTP Port?

Quarkus includes the “undertow” extension which is triggered when you include a JAXRS dependency in your project. We will see in this tutorial which are the most common settings you can apply to a Quarkus application to configure the embedded Undertow server.

First of all let’s specify how you can set configuration parameters on Quarkus. You have two main options:

  • Pass parameters on the Command Line using -D
  • Include parameters in the the application.properties file

Configuring HTTP Port

The most basic setting is the default HTTP Port, which can be overridden as follows in the application.properties file:

quarkus.http.port=9080

Configuring Host name

Next, here is how you can configure in the application.properties file the Host where the application will be bound (“Default is 0.0.0.0”):

quarkus.http.host=acme.com

Configuring HTTPS Port

In order to configure the secure port used for https connections, you can use this setting:

quarkus.http.ssl-port=8443

Configuring HTTP Test Port

Quarkus tests by default run on port 8081 to avoid conflict with the HTT Port. You can can configure the port used by tests by setting quarkus.http.test-port as follows:

quarkus.http.test-port=8088

Configuring the Context Path

Finally, another common setting is the Context Path, which defaults to the Root Context (“/”). Here is how to set it to a custom value:

quarkus.servlet.context-path=/webcontext

Configuring CORS

Cross-origin resource sharing is a mechanism that allows some restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served. In order to enable Cross-origin resource sharing you need to set the quarkus.http.cors property to true, which enables it through a Servlet Filter. Then, you can fine tune the CORS policy to allow specific headers or HTTP methods to be allowed.

Here is a fully configured application.properties which allows all HTTP methods from the site acme.com:

quarkus.http.cors=true
quarkus.http.cors.origins=http://foo.com,http://www.acme.com
quarkus.http.cors.methods=GET,PUT,POST,DELETE
quarkus.http.cors.headers=X-Custom
quarkus.http.cors.exposed-headers=Content-Disposition

Configuring IO Threads

The number if IO threads used to perform IO in Quarkus will be automatically set to a reasonable value based on the number of CPU cores. You can configure it using the following property:

quarkus.http.io-threads=5

Configuring HTTPS with Quarkus

You can configure secure connections for Web applications either with a PEM certificate or using a Keystore. Let’s see how to configure a keystore. First generate a self-signed keystore:

keytool -genkey -keyalg RSA -alias demo -keystore keystore.jks -storepass mypassword -validity 365 -keysize 2048

Then, provide in your configuration file a reference to the keystore and its password:

quarkus.http.ssl.certificate.key-store-file=/path/to/keystore.jks quarkus.http.ssl.certificate.key-store-password=mypassword

quarkus.http.ssl.certificate.key-store-file=/path/to/keystore.jks 
quarkus.http.ssl.certificate.key-store-password=mypassword

In order to configure the secure port used for https connections, you can use this setting:

quarkus.http.ssl-port=8443

A practical guide for testing Quarkus applications

In this article we will learn how to Test Quarkus applications using the REST Assured API. We will also explore advanced Integration Test examples and automatic Transaction Rollback for your Tests.

Overview of Quarkus Testing

Quarkus applications can be tested with any Java based testing framework. As most Quarkus applications are built upon a RESTful layer, the framework REST Assured has become a standard for Quarkus applications.

REST Assured is a Java library that can be used to write powerful tests for REST APIs using a flexible Domain Specific Language (DSL). The fluent API available in REST Assured supports the standard patterns from Behavior-Driven Development (BDD) with its Given/When/Then syntax. The resulting test is simple to read and can include all the steps needed to build the test with just a one line of code.

Creating a basic Test

We will be testing the following CRUD REST Endpoint:

@Path("/customer")
@Produces("application/json")
@Consumes("application/json")
public class CustomerResource {

    @Inject
    EntityManager entityManager;

    @GET
    public Customer[] get() {
        return entityManager.createNamedQuery("Customers.findAll", Customer.class)
                .getResultList().toArray(new Customer[0]);
    }
    @POST
    @Transactional
    public Response create(Customer customer) {
        if (customer.getId() != null) {
            throw new WebApplicationException("Id was invalidly set on request.", 422);
        }

        entityManager.persist(customer);
        System.out.println("Created "+customer);
        return Response.ok(customer).status(201).build();
    }

    @PUT
    @Transactional
    public Customer update(Customer customer) {
        if (customer.getId() == null) {
            throw new WebApplicationException("Customer Id was not set on request.", 422);
        }

        Customer entity = entityManager.find(Customer.class, customer.getId());

        if (entity == null) {
            throw new WebApplicationException("Customer with id of " + customer.getId() + " does not exist.", 404);
        }

        entity.setName(customer.getName());

        return entity;
    }

    @DELETE
    @Transactional
    public Response delete(Customer customer) {
        Customer entity = entityManager.find(Customer.class, customer.getId());
        if (entity == null) {
            throw new WebApplicationException("Customer with id of " + customer.getId() + " does not exist.", 404);
        }
        entityManager.remove(entity);
        return Response.status(204).build();
    }
}

The most basic test of this application is to check if the “/customer” Endpoint is available:

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;

@QuarkusTest
public class CustomerEndpointTest {

    @Test
    public void testCustomerService() {

        RestAssured.given()
                .when().get("/customer")
                .then()
                .statusCode(200);
    }
}

The @QuarkusTest annotation is built upon junit5, therefore to use it in combination with RestAssured fluent API, you need to following dependency in your application:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

As far as configuration is concerned, the most common test settings you can plug in your configuration are:

quarkus.http.test-port=8083
quarkus.http.test-timeout=10s

Building an Advanced Quarkus Test class

Now let’s build a more complex test which checks the content of the Body returned by the REST service.

Within our example application, we have added an import.sql file with some default data:

INSERT INTO Customer(id, name) VALUES (1, 'Batman');
INSERT INTO Customer(id, name) VALUES (2, 'Superman');
INSERT INTO Customer(id, name) VALUES (3, 'Wonder woman');

Now let’s build a test for that:

@QuarkusTest
@TestHTTPEndpoint(CustomerResource.class)
public class CustomerEndpointAdvancedTest {

    @Test
    public void testCustomerService() {

        RestAssured.given()
                .when().get()
                .then()
                .statusCode(200)
                .body("$.size()", is(3),
                        "[0].id", is(1),
                        "[0].name", is("Batman"),
                        "[1].id", is(2),
                        "[1].name", is("Superman"),
                        "[2].id", is(3),
                        "[2].name", is("Wonder woman")
                );


        given()
                .contentType("application/json")
                .body(new Customer("Iron man"))
                .when()
                .post()
                .then()
                .statusCode(201);

         RestAssured.given()
                .when().get()
                .then()
                .statusCode(200)
                .body("$.size()", is(4),
                        "[3].name", is("Iron man")
                );

    }
}

First of all, notice we have now used the @TestHTTPEndpoint(CustomerResource.class). This allows us to use the default Path for the CustomerResource. This way we have decoupled the Endpoint from its Path so if we change the Path of the Customer resource we won’t have to change our Testing classes too.

Next, pay attention on how we have verified the content of the Response Body:

        RestAssured.given()
                .when().get()
                .then()
                .statusCode(200)
                .body("$.size()", is(3),
                        "[0].id", is(1),
                        "[0].name", is("Batman"),
                        "[1].id", is(2),
                        "[1].name", is("Superman"),
                        "[2].id", is(3),
                        "[2].name", is("Wonder woman")
                );

Since our customer HTTP GET returns an array of Customer objects, we can check each element of the array using the [n] index and the class property. Additionally, we are also asserting that the number of elements returned equals to 3.

Then, notice how we can easily post a Java object into the body by setting the content type accordingly (in our case JSON):

        given()
                .contentType("application/json")
                .body(new Customer("Iron man"))
                .when()
                .post()
                .then()
                .statusCode(201);

Testing an HTTP Resource of your Quarkus application

The next test example shows how to check the content of a Web resource using the @TestHTTPResource annotation. This annotation is bound to a java.net.URL resource. Therefore you can read the HTTP resource as an InputStream and verify the content of the page. In this example, we are checking that the HTML page contains the title “Quarkus-hibernate example”:

@QuarkusTest
public class CustomerEndpointHTTPTest {
    @TestHTTPResource("index.html")
    URL url;

    @Test
    public void testIndexHtml() throws Exception {
        try (InputStream in = url.openStream()) {
            String contents = readStream(in);
            Assertions.assertTrue(contents.contains("<title>Quarkus-hibernate example</title>"));
        }
    }

    private static String readStream(InputStream in) throws IOException {
        byte[] data = new byte[1024];
        int r;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        while ((r = in.read(data)) > 0) {
            out.write(data, 0, r);
        }
        return new String(out.toByteArray(), StandardCharsets.UTF_8);
    }
}

Adding callbacks for each test

You can receive callbacks each time a single test is run by implementing the QuarkusTestBeforeEachCallback and QuarkusTestAfterEachCallback interface. See the following example:

import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback;
import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback;
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;

public class TestCallBackExample implements QuarkusTestBeforeEachCallback, QuarkusTestAfterEachCallback {

    public void beforeEach(QuarkusTestMethodContext context) {
        System.out.println("Executing " + context.getTestMethod());
    }
    public void afterEach(QuarkusTestMethodContext context) {
        System.out.println("Executed " + context.getTestMethod());
    }
}

In order to register the CallBack classes, you have to add a file under the folder resources/META-INF/services with the name of the interface:

$ tree src/main/resources/
src/main/resources/
├── application.properties
├── import.sql
└── META-INF
    ├── resources
    │   └── index.html
    └── services
        ├── io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
        └── io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback

Within each file, you need to specify the implementing class, which is in our example (for both files):

org.acme.TestCallBackExample

Using a specific profile for your Quarkus Tests

Out of the box Quarkus includes the following configuration profiles:

  • dev – Triggered when running in development mode (i.e. quarkus:dev)
  • test – Triggered when running tests
  • prod – This is picked up when not running in development or test mode

You can use the following syntax to bind a configuration parameter to a specific profile:

%{profile}.config.key=value

As an example, you can use the following application.properties configuration file, which defines multiple profiles in it:

%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgresDev
%test.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:6432/postgresTest
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:7432/postgresProd

In the above configuration, we have specified three different JDBC URLs for our Datasource connection. Each one is bound to a different profile. This way, you can use a configuration specific for the test profile.

Besides that, you can also define custom profiles by implementing the io.quarkus.test.junit.QuarkusTestProfile as in this example:

import io.quarkus.test.junit.QuarkusTestProfile;

public class CustomProfile implements  QuarkusTestProfile {

    @Override
    public Map<String, String> getConfigOverrides() {
        return Map.of("message", "Hi there!");
    }

    @Override
    public String getConfigProfile() {
        return "custom-profile";
    }
}

To use the custom profile in your tests, just add the @TestProfile with a reference to your Profile class:

import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
@TestHTTPEndpoint(CustomerResource.class)
@TestProfile(CustomProfile.class)
public class CustomProfileTest {

    @Test
    public void testCustomProfile() {

        given()
                .when().get("/hello")
                .then()
                .statusCode(200)
                .body(is("Hi there!"));


    }
}

How to plug a Database in your Integration tests

Our example application uses a Database for reading/writing data. The simplest way to perform integration tests with a Database is to run the DB as Docker process. If you don’t want to manually start and stop your container, you an use the “docker-maven-plugin” which lets you bind the start and stop of the Docker image accordingly with the Maven execution. Here is how to add PostgreSQL to our test example:

      <plugin>
        <!-- Automatically start PostgreSQL for integration testing - requires Docker -->
        <groupId>io.fabric8</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>${docker-plugin.version}</version>
        <configuration>
          <skip>false</skip>
          <images>
            <image>
              <name>postgres:10.5</name>
              <alias>postgresql</alias>
              <run>
                <env>
                  <POSTGRES_USER>quarkus</POSTGRES_USER>
                  <POSTGRES_PASSWORD>quarkus</POSTGRES_PASSWORD>
                  <POSTGRES_DB>quarkusdb</POSTGRES_DB>
                </env>
                <ports>
                  <port>5432:5432</port>
                </ports>
                <log>
                  <prefix>PostgreSQL: </prefix>
                  <date>default</date>
                  <color>cyan</color>
                </log>
                <wait>
                  <tcp>
                    <mode>mapped</mode>
                    <ports>
                      <port>5432</port>
                    </ports>
                  </tcp>
                  <time>10000</time>
                </wait>
              </run>
            </image>
          </images>
        </configuration>
        <executions>
          <execution>
            <id>docker-start</id>
            <phase>test-compile</phase>
            <goals>
              <goal>stop</goal>
              <goal>start</goal>
            </goals>
          </execution>
          <execution>
            <id>docker-stop</id>
            <phase>post-integration-test</phase>
            <goals>
              <goal>stop</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

Rolling back Test Transactions

If your Test suite includes transactions, you can decorate your Test with the io.quarkus.test.TestTransaction annotation. This annotation will trigger the automatic rollback of all changes to the database when the method completes.

For example, the following method persists a new customer, verifies that the sequence id is not null. Then, when the method completes, the Customer insert will be rolled back:

@QuarkusTest
@TestTransaction
public class CustomerRepositoryTests {

    @Inject
    CustomerRepository CustomerRepository;

    @Test
    void addCustomer() {
        Customer c= new Customer();
        c.name = "john";
        c.mail = "john@gmail.com";
        CustomerRepository.persist(c);
        Assertions.assertNotNull(c.id);
    }
}

Source code

The source code for this example is available: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/testing

Zero Config Database configuration with Quarkus (DevServices)

Quarkus 1.13 ships with a new feature called “DevServices” which allows testing or running in dev mode Quarkus without an actual database configuration. Basically, all you need to do is including the extension in your pom.xml file and Quarkus will set up the playground for you. No need to start or configure a database!

Let’s see an example. The Hibernate ORM example has been modified for this purpose: https://github.com/quarkusio/quarkus-quickstarts/tree/main/hibernate-orm-quickstart

If you check the application.properties, you will see that a configuration has been added only for the production profile:

%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=quarkus_test
%prod.quarkus.datasource.password=quarkus_test
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkus_test
%prod.quarkus.datasource.jdbc.max-size=8
%prod.quarkus.datasource.jdbc.min-size=2

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.sql-load-script=import.sql

Within the pom.xml, Quarkus will find PostgreSQL extension so that will be used:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>

In order to use the DevServices feature you need to start the Docker service so that the container image of the Database is pulled and started with the required settings. The only exception is H2 Database which will run as In-Process application. Besides it, DB2 and MSSQL require you to include a license acceptance file named src/main/resources/container-license-acceptance.txt in your project.

An example file is shown below:

ibmcom/db2:11.5.0.0a
mcr.microsoft.com/mssql/server:2017-CU12

Enough talking. Let’s see this feature in action. First we start the Docker daemon:

$ service docker start

Now you can run/test the application as follows:

$ mvn install quarkus:dev

The Test Phase will kick-in, then the application is launched:

2021-05-09 19:10:02,999 INFO  [org.tes.doc.DockerClientProviderStrategy] (build-13) Found Docker environment with local Unix socket (unix:///var/run/docker.sock)
2021-05-09 19:10:03,000 INFO  [org.tes.DockerClientFactory] (build-13) Docker host IP address is localhost
2021-05-09 19:10:03,023 INFO  [org.tes.DockerClientFactory] (build-13) Connected to docker: 
  Server Version: 1.13.1
  API Version: 1.26
  Operating System: Fedora 29 (Workstation Edition)
  Total Memory: 31542 MB
2021-05-09 19:10:03,025 INFO  [org.tes.uti.ImageNameSubstitutor] (build-13) Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2021-05-09 19:10:03,796 INFO  [org.tes.DockerClientFactory] (build-13) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2021-05-09 19:10:03,796 INFO  [org.tes.DockerClientFactory] (build-13) Checking the system...
2021-05-09 19:10:03,796 INFO  [org.tes.DockerClientFactory] (build-13) ✔︎ Docker server version should be at least 1.6.0
2021-05-09 19:10:03,995 INFO  [org.tes.DockerClientFactory] (build-13) ✔︎ Docker environment should have more than 2GB free disk space
2021-05-09 19:10:04,013 INFO  [🐳 .2]] (build-13) Creating container for image: postgres:13.2
2021-05-09 19:10:04,090 INFO  [🐳 .2]] (build-13) Starting container with ID: 620cd106ce403136bc6e8de84b0cca691f17c421a15315582bcb6a977c878d40
2021-05-09 19:10:04,500 INFO  [🐳 .2]] (build-13) Container postgres:13.2 is starting: 620cd106ce403136bc6e8de84b0cca691f17c421a15315582bcb6a977c878d40
2021-05-09 19:10:06,285 INFO  [🐳 .2]] (build-13) Container postgres:13.2 started in PT2.289385S
Hibernate: 
    
    drop table if exists known_fruits cascade
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-05-09 19:10:07,241 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (Quarkus Main Thread) SQL Warning Code: 0, SQLState: 00000
2021-05-09 19:10:07,242 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (Quarkus Main Thread) table "known_fruits" does not exist, skipping
Hibernate: 
    
    drop sequence if exists known_fruits_id_seq
2021-05-09 19:10:07,242 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (Quarkus Main Thread) SQL Warning Code: 0, SQLState: 00000
2021-05-09 19:10:07,243 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (Quarkus Main Thread) sequence "known_fruits_id_seq" does not exist, skipping
Hibernate: create sequence known_fruits_id_seq start 10 increment 1
Hibernate: 
    
    create table known_fruits (
       id int4 not null,
        name varchar(40),
        primary key (id)
    )
Hibernate: 
    
    alter table if exists known_fruits 
       add constraint UK_57g3m8wr3qxoj706a6hsqg6ye unique (name)
Hibernate: 
    INSERT INTO known_fruits(id, name) VALUES (1, 'Cherry')
Hibernate: 
    INSERT INTO known_fruits(id, name) VALUES (2, 'Apple')
Hibernate: 
    INSERT INTO known_fruits(id, name) VALUES (3, 'Banana')
2021-05-09 19:10:07,510 INFO  [io.quarkus] (Quarkus Main Thread) hibernate-orm-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.3.Final) started in 5.610s. Listening on: http://localhost:8080
2021-05-09 19:10:07,512 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-05-09 19:10:07,512 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, mutiny, narayana-jta, resteasy, resteasy-jackson, s

As you can see, the PostgreSQL image has been

$ docker images

REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
docker.io/postgres              13.2                26c8bcd8b719        4 weeks ago         314 MB

We have just covered a new amazing Quarkus feature that greatly simplifies testing and developing database applications.

Getting started with MongoDB and Quarkus

This tutorial covers the all the steps required for creating a REST application with MongoDB NoSQL Database and Quarkus.

MongoDB is a document-oriented NoSQL database which became popular in the last decade. It can be used for high volume data storage as a replacement for relational databases. Instead of using tables and rows, MongoDB makes use of collections and documents. A Document consists of key-value pairs which are the basic unit of data in MongoDB. A Collection, on the other hand, contain sets of documents and function which is the equivalent of relational database tables.

In order to get started with MongoDB, you can download the latest Community version from: https://www.mongodb.com/try/download/community

To simplify things, we will start MongoDB using Docker with just one line:

docker run -ti --rm -p 27017:27017 mongo:4.0

Done with MongoDB, there are basically two approaches for developing MongoDB applications and Quarkus:

  • Using the MongoDB Java Client which focuses on the API provided by the MongoDB Client
  • Using Hibernate Panache which focuses on a special kind of Entity objects which are named PanacheMongoEntity

Using the MongoDB Java Client API

By using this approach, you manipulate plain Java Bean classes with MongoDB Java Client. Let’s build a sample application. We will start from the simple Model class:

public class Customer {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Then, we need a Service class to manage the standard CRUD operations on the Model using the MongoDB Client:

@ApplicationScoped
public class CustomerService {

    @Inject MongoClient mongoClient;

    public List<Customer> list(){
        List<Customer> list = new ArrayList<>();
        MongoCursor<Document> cursor = getCollection().find().iterator();

        try {
            while (cursor.hasNext()) {
                Document document = cursor.next();
                Customer customer = new Customer();
                customer.setName(document.getString("name"));
                customer.setId(document.getLong("id"));
                list.add(customer);
            }
        } finally {
            cursor.close();
        }
        return list;
    }

    public void add(Customer customer){
        Document document = new Document()
                .append("name", customer.getName())
                .append("id", customer.getId());
        getCollection().insertOne(document);
    }

    public void update(Customer customer){
        Bson filter = eq("id", customer.getId());
        Bson updateOperation = set("name", customer.getName());
        getCollection().updateOne(filter, updateOperation);
    }

    public void delete(Customer customer){
        Bson filter = eq("id", customer.getId());
        getCollection().deleteOne(filter);
    }
    private MongoCollection getCollection(){
        return mongoClient.getDatabase("customer").getCollection("customer");
    }
}

Finally, a REST Endpoint is added to expose the Service:

@Path("/customer")
@Produces("application/json")
@Consumes("application/json")
public class CustomerEndpoint {

    @Inject CustomerService service;

    @GET
    public List<Customer> list() {
        return service.list();
    }

    @POST
    public List<Customer> add(Customer customer) {
        service.add(customer);
        return list();
    }

    @PUT
    public List<Customer> put(Customer customer) {
        service.update(customer);
        return list();
    }

    @DELETE
    public List<Customer> delete(Customer customer) {
        service.delete(customer);
        return list();
    }
}

The last configuration tweak required is in application.properties where we will configure the MongoDB client for a single instance on localhost:

quarkus.mongodb.connection-string = mongodb://localhost:27017

To compile the application, besides the resteasy libraries and the testing API, we need to include the Quarkus MongoDB Client API:

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-mongodb-client</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>

Run the application:

mvn install quarkus:dev

Then, you can test adding a new Customer:

curl -d '{"id":"1", "name":"Frank"}' -H "Content-Type: application/json" -X POST http://localhost:8080/customer

Checking the list of customers:

curl http://localhost:8080/customer
[{"id":1,"name":"Frank"}]

Updating a Customer:

curl -d '{"id":"1", "name":"John"}' -H "Content-Type: application/json" -X PUT http://localhost:8080/customer

And finally deleting it:

curl -d '{"id":"1"}' -H "Content-Type: application/json" -X DELETE http://localhost:8080/customer

Using MongoDB with Hibernate Panache

The Hibernate Panache API greatly simplifies the management of CRUD operations on Hibernate Entity by extending them with Panache Entity Objects and their subclasses such as PanacheMongoEntity.

The following here is the Customer class, which extends PanacheMongoEntity and therefore inherits all the basic CRUD operations:

@MongoEntity(collection = "customers")
public class Customer extends PanacheMongoEntity {
    
    public Long id;

    @BsonProperty("customer_name")
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public static Customer findByName(String name) {
        return find("name", name).firstResult();
    }

}

The @MongoEntity annotation can be used to define the MongoDB collection name to be used for the Entity class. Also, the @BsonProperty annotation is used to change the name used in MongoDB to persist the field. With the PanacheMongoEntity, we already have all the methods available to perform the standard CRUD operations. The REST Endpoint is therefore just a wrapper to access each CRUD operation:

@Path("/customer")
@Produces("application/json")
@Consumes("application/json")
public class CustomerEndpoint {

    @GET
    public List<Customer> list() {
        return Customer.listAll();
    }

    @POST
    public Response create(Customer customer) {
        customer.persist();
        return Response.status(201).build();
    }

    @PUT
    public void update(Customer customer) {
        customer.update();
    }

    @DELETE
    public void delete(Customer c) {
        Customer customer = Customer.findById(c.id);
        customer.delete();
    }
}

The last configuration tweak required is in application.properties where we will configure the MongoDB client for a single instance on localhost and the database name:

quarkus.mongodb.connection-string = mongodb://localhost:27017
quarkus.mongodb.database = customers

To build this example, you will need to use the following dependency in your pom.xml (instead of of the MongoDB client API):

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-mongodb-panache</artifactId>
    </dependency>

Using PanacheQuery to fetch your data

The PanacheQuery interface can be used to map an Entity query, adding the use of paging, getting the number of results, and operating both on List and Java Stream API.

Here is an example:

    // Get first page of 20 entries
    @Path("/page1")
    @GET
    public List<Customer> pageList() {
        PanacheQuery<Customer> customers = Customer.findAll();
        customers.page(Page.ofSize(20));
        return customers.list();
    }

The above example shows how you can fetch just the first page of 20 Customer Entity objects. To fetch the next page:

List<Customer> nextPage = customers.nextPage().list();

Besides the findAll() method you can also restrict your Query using the find(key,value) method. For example:

    @Path("/page1")
    @GET
    public List<Customer> pageList() {
        PanacheQuery<Customer> customers = Customer.find("name","John");
        customers.page(Page.ofSize(20));
        return customers.list();
    }

Finally, the PanacheQuery can be used as well to fetch the total number of entities, without paging:

int count = customers.count();

If you want an example of Hibernate Panache with a Relational Database check the following article: Managing Data Persistence with Quarkus and Hibernate Panache

You can find the source code for both examples on Github at: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/mongodb

How to generate a JAX-RS CRUD application in Quarkus using Panache

In this tutorial we will learn how to generate automatically a JAX-RS CRUD application in Quarkus, starting from a Hibernate Panache Entity.

Creating CRUD applications for simple REST endpoint is a tedious task which requires adding lots of boilerplate code. Thanks to the quarkus-hibernate-orm-panache extension, this can be self-generated starting from a plain Entity class using or not a Respository class for persistence.

Please note that this feature is still experimental and currently supports Hibernate ORM with Panache and can generate CRUD resources that work with application/json and application/hal+json content.

First, check this tutorial for some background on Hibernate Panache: Managing Data Persistence with Quarkus and Hibernate Panache

Setting up the Quarkus project

We will now re-create the same application using self-generation of Resource Endpoints. Start by creating a new Quarkus project:

mvn io.quarkus:quarkus-maven-plugin:1.6.1.Final:create \
     -DprojectGroupId=com.mastertheboss \
     -DprojectArtifactId=panache-demo \
     -DclassName="com.mastertheboss.MyService" \
     -Dpath="/tickets" \
     -Dextensions="quarkus-hibernate-orm-rest-data-panache,quarkus-hibernate-orm-panache,quarkus-jdbc-postgresql,quarkus-resteasy-jsonb"

As you can see, our pom.xml file includes an extra dependency to support JAX-RS self-generation with Panache:

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

We will be adding a simple Entity objects, named Tickets which is unchanged from our first Panache example:

import javax.persistence.Column;
import javax.persistence.Entity;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
public class Ticket extends PanacheEntity {

    @Column(length = 20, unique = true)
    public String name;

    @Column(length = 3, unique = true)
    public String seat;

    public Ticket() {
    }

    public Ticket(String name, String seat) {
        this.name = name;
        this.seat = seat;
    }
}

Then, you have two options here. If you are updating the Entity directly from the JAX-RS Resource, then just add an interface which extends PanacheEntityResource which is typed with the Entity and the Primary Key used behind the hoods by Panache (in our example, we default to Long)

import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;

public interface MyService  extends PanacheEntityResource<Ticket, Long> {
}

On the other hand, if your application uses a Repository Class, then you will use a different set of Types which includes also the TicketRepository class:

public interface MyService extends PanacheRepositoryResource<TicketRepository, Ticket, Long> {
}

That’s all! You just add one typed interface and you are done with the JAX-RS Resource! As a proof of concept, let’s add openapi extension to our project so that we can eventually check all endpoints which have been created:

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-openapi</artifactId>
    </dependency>

Since our application will run against PostgreSQL, in our application.properties includes the JDBC settings for connecting to a local instance of it:

quarkus.datasource.url=jdbc:postgresql:quarkusdb
quarkus.datasource.driver=org.postgresql.Driver
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

Now start PostgreSQL, for example using Docker:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=quarkus -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=quarkusdb -p 5432:5432 postgres:10.5

We also have included an import.sql file to create some sample records:

INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Phantom of the Opera','11A');
INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Chorus Line','5B');
INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Mamma mia','21A');

And the following class will run a minimal Test Case:

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
public class MyServiceTest {

    @Test
    public void testListAllTickets() {

        given()
                .when().get("/my-service")
                .then()
                .statusCode(200)
                .body(
                        containsString("Phantom of the Opera"),
                        containsString("Chorus Line"),
                        containsString("Mamma mia")
                );

    }

}

Testing the application

Now if you run the application using:

mvn install quarkus:dev

You will see the following endpoints are available at: http://localhost:8080/openapi

openapi: 3.0.1
info:
  title: Generated API
  version: "1.0"
paths:
  /my-service:
    get:
      responses:
        "200":
          description: OK
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Ticket'
      responses:
        "200":
          description: OK
  /my-service/{id}:
    get:
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Ticket'
    put:
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Ticket'
      responses:
        "200":
          description: OK
    delete:
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      responses:
        "204":
          description: No Content
components:
  schemas:
    Ticket:
      type: object
      properties:
        id:
          format: int64
          type: integer
        name:
          type: string
        seat:
          type: string

As you can see, our JAX-RS Service has been bound with the hypened name of the Backing resource: “my-service”. As a proof of concept, you can for example query the list of Ticket objects:

curl http://localhost:8080/my-service

That will return:

[
   {
      "id":2,
      "name":"Chorus Line",
      "seat":"5B"
   },
   {
      "id":3,
      "name":"Mamma mia",
      "seat":"21A"
   },
   {
      "id":1,
      "name":"Phantom of the Opera",
      "seat":"11A"
   }
]

That’s all. We have demonstrated how to scaffold a CRUD JAX-RS application from an Entity object using Quarkus and Hibernate ORM Panache. Stay tuned for more updates on this matter: https://quarkus.io/guides/rest-data-panache

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/panache-rest-demo

Building Quarkus native applications with Mandrel

Mandrel is a downstream open source distribution of GraalVM edition which can be used to create native builds for Quarkus applications. Quarkus applications require one essential tool of GraalVM – the native-image feature – which is what actually produces native executables. Mandrel let us to have GraalVM bundled on top of OpenJDK 11 in RHEL distributions and other OpenJDK 11 distributions. Thus, Mandrel can best be described as a distribution of a regular OpenJDK with a specially packaged GraalVM native image.

Mandrel releases are built from the upstream GraalVM code base, with just a few minor changes. Mandrel supports the same native image capability as GraalVM with no significant changes to functionality. One minor difference is that Mandrel does not include support for Polyglot programming languages via the Truffle interpreter and compiler framework. Therefore, it is not possible to extend Mandrel by downloading languages from the Truffle language catalogue.

Mandrel is also built using the standard OpenJDK project release of jdk11u, therefore it does not include a few small enhancements added by Oracle to the JVMCI module which allows the Graal compiler to be run inside OpenJDK.

That being said, these differences should not cause the resulting images themselves to execute in a noticeably different manner.

Installing Mandrel

In order to install, you need at first to install the following packages which are required for Mandrel’s native-image tool:

On Fedora/CentOS/RHEL machines:

sudo dnf install glibc-devel zlib-devel gcc libffi-devel

On Ubuntu systems with:

sudo apt install gcc zlib1g-dev libffi-dev

Then, download the latest release of Mandrel from: https://github.com/graalvm/mandrel/releases

In our case, we will download mandrel-java11-linux-amd64-20.1.0.0.Alpha1.tar.gz:

Then, untar the distribution:

$ tar -xf mandrel-java11-linux-amd64-20.1.0.0.Alpha1.tar.gz

The folder mandrelJDK will be created as top JDK directory:

$ ls mandrelJDK
bin  conf  demo  include  jmods  languages  legal  lib  LICENSE  man  README.md  release  SECURITY.md  THIRD_PARTY_LICENSE.txt

Now you need to set up the JAVA_HOME, GRAALVM_HOME and PATH to include the folder mandrelJDK:

$ export JAVA_HOME="$( pwd )/mandrelJDK"
$ export GRAALVM_HOME="${JAVA_HOME}"
$ export PATH="${JAVA_HOME}/bin:${PATH}"

Check that your Java version is built on the top of OpenJDK 11.08:

java -version
openjdk version "11.0.8-ea" 2020-07-14
OpenJDK Runtime Environment 18.9 (build 11.0.8-ea+7)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.8-ea+7, mixed mode)

Building a Quarkus native application with Mandrel

Now you can try to build a sample Quarkus application as native executable:

$ curl -O -J  https://code.quarkus.io/api/download
$ unzip code-with-quarkus.zip
$ cd code-with-quarkus/
$ ./mvnw package -Pnative
$ ./target/code-with-quarkus-1.0.0-SNAPSHOT-runner

The following output will be displayed:

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-07-11 18:45:40,939 INFO  [io.quarkus] (main) code-with-quarkus 1.0.0-SNAPSHOT native (powered by Quarkus 1.6.0.Final) started in 0.012s. Listening on: http://0.0.0.0:8080
2020-07-11 18:45:40,939 INFO  [io.quarkus] (main) Profile prod activated. 
2020-07-11 18:45:40,939 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

Check that the “hello” Endpoint:

$ curl http://localhost:8080/hello
hello

Great. You just managed to build your native executable application using Quarkus and Mandrel distribution.

How to connect your Quarkus application to Infinispan

Infinispan is a distributed in-memory key/value data grid. An in-memory data grid is a form of middleware that stores sets of data for use in one or more applications, primarily in memory. There are different clients available to connect to a remote/embedded Infinispan server, In this tutorial we will learn how to connect to Infinispan using Quarkus extension.

Starting Infinispan

For the purpose of this tutorial, we will be running a local Infinispan 10 server with this cache definition in infinispan.xml:

  <cache-container default-cache="local">
      <transport cluster="${infinispan.cluster.name}" stack="${infinispan.cluster.stack:tcp}" node-name="${infinispan.node.name:}"/>
      <local-cache name="local"/>
      <invalidation-cache name="invalidation" mode="SYNC"/>
      <replicated-cache name="repl-sync" mode="SYNC"/>
      <distributed-cache name="dist-sync" mode="SYNC"/>
   </cache-container>

From the bin folder of Infinispan, run:

 ./server.sh

As an alternative, you can also run Infinispan with Docker as follows:

docker run -it -p 11222:11222 infinispan/server:latest

Creating the Quarkus Infinispan project

In order to create the Quarkus application, we will need the following set of dependencies:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-infinispan-client</artifactId>
</dependency>

Resteasy and jsonb will be needed to set up a CRUD REST application. The dependency quarkus-infinispan-client is needed to connect to Infinispan from Quarkus

There is an extra dependency needed in order to start and connect to an embedded Infinispan server, however we will be discussing this later.

Here is our main Application class:

@ApplicationScoped
public class InfinispanClientApp {

    private static final Logger LOGGER = LoggerFactory.getLogger("InfinispanClientApp");
    @Inject
    @Remote("local")
    RemoteCache<String, Customer> cache;

    void onStart(@Observes StartupEvent ev) {
        cache.addClientListener(new EventPrintListener());
        Customer c = new Customer("1","John","Smith");
        cache.put("1", c);
    }

    @ClientListener
    static class EventPrintListener {

        @ClientCacheEntryCreated
        public void handleCreatedEvent(ClientCacheEntryCreatedEvent e) {
            LOGGER.info("Someone has created an entry: " + e);
        }

        @ClientCacheEntryModified
        public void handleModifiedEvent(ClientCacheEntryModifiedEvent e) {
            LOGGER.info("Someone has modified an entry: " + e);
        }

        @ClientCacheEntryRemoved
        public void handleRemovedEvent(ClientCacheEntryRemovedEvent e) {
            LOGGER.info("Someone has removed an entry: " + e);
        }

    }
}

A couple of things to notice:

When the application is bootstrapped, we are connecting to the RemoteCache (“local”), setting up a ClientListener on it and we also add one key into it.

As we need to marshall/unmarshall the Java class Customer using Infinispan, we will use annotation based Serialization by setting the @ProtoField annotation on fields which need to be serialized:

public class Customer {
    private String id;
    private String name;
    private String surname;
    
    @ProtoField(number = 1)
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
    
    @ProtoFactory
    public Customer(String id, String name, String surname) {
        this.id = id;
        this.name = name;
        this.surname = surname;
    }

    @ProtoField(number = 2)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    @ProtoField(number = 3)
    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Customer)) return false;
        Customer customer = (Customer) o;
        return Objects.equals(id, customer.id) &&
                Objects.equals(name, customer.name) &&
                Objects.equals(surname, customer.surname);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, surname);
    }

    public Customer() {
    }
}

Then we need also a SerializationContextInitializer interface with an annotation on it to specify configuration settings:

@AutoProtoSchemaBuilder(includeClasses = { Customer.class}, schemaPackageName = "customer_list")
public interface CustomerContextInitializer extends SerializationContextInitializer {
}

That’s it. We are done with Infinispan. We will now add a REST Endpoint to allow CRUD Operations on our RemoteCache:

Path("/infinispan")
@ApplicationScoped
public class InfinispanEndpoint {

    @Inject
    @Remote("local")
    RemoteCache<String, Customer> cache;


    @GET
    @Path("{customerId}")
    @Produces("application/json")
    public Response get(@PathParam("customerId") String id) {
        Customer customer = cache.get(id);
        System.out.println("Got customer " +customer);
        return Response.ok(customer).status(200).build();
    }



    @POST
    @Produces("application/json")
    @Consumes("application/json")
    public Response create(Customer customer) {
        cache.put(customer.getId(), customer);
        System.out.println("Created customer " +customer);
        return Response.ok(customer).status(201).build();
    }

    @PUT
    @Produces("application/json")
    @Consumes("application/json")
    public Response update(Customer customer) {
        cache.put(customer.getId(), customer);
        System.out.println("Updated customer " +customer);
        return Response.ok(customer).status(202).build();
    }

    @DELETE
    @Path("{customerId}")
    public Response delete(@PathParam("customerId") String id) {
        cache.remove(id);
        System.out.println("Deleted customer "+id);
        return Response.ok().status(202).build();
    }
}

As you can see, once that we get injected the RemoteCache, we can apply the basic get/put/remove operations accordingly on the HTTP methods.

Finally, into our application.properties, we have set the address of Infinispan server:

quarkus.infinispan-client.server-list=localhost:11222

Running the Quarkus application

Start the Quarkus application in dev mode:

mvn clean install quarkus:dev

We can test our application using the REST Endpoints. For example, to get the customer with id “1”:

curl http://localhost:8080/infinispan/1
{"id":"1","name":"John","surname":"Smith"}

To add a new Customer:

curl -d '{"id":"2", "name":"Clark","surname":"Kent"}' -H "Content-Type: application/json" -X POST http://localhost:8080/infinispan

To update an existing Customer:

curl -d '{"id":"2", "name":"Peter","surname":"Parker"}' -H "Content-Type: application/json" -X PUT http://localhost:8080/infinispan

And finally to delete one Customer:

curl -X DELETE http://localhost:8080/infinispan/2

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/infinispan-demo

Embedding Infinispan in Quarkus applications

It is also possible to bootstrap an Infinispan cluster from within a Quarkus application using the embedded API. For this purpos you will need an extra dependency in your pom.xml:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-infinispan-embedded</artifactId>
</dependency>

With this dependency in place, you can inject the org.infinispan.manager.EmbeddedCacheManager in your code:

@Inject
EmbeddedCacheManager emc;

This will give us a default local (i.e. non-clustered) CacheManager. You can also build a cluster of servers from a configuration file, say dist.xml:

ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.clustering().cacheMode(CacheMode.DIST_SYNC);

List<EmbeddedCacheManager> managers = new ArrayList<>(3);
try {
    String oldProperty = System.setProperty("jgroups.tcp.address", "127.0.0.1");
    for (int i = 0; i < 3; i++) {
        EmbeddedCacheManager ecm = new DefaultCacheManager(
                Paths.get("src", "main", "resources", "dist.xml").toString());
        ecm.start();
        managers.add(ecm);
        // Start the default cache
        ecm.getCache();
    }

And here’s a sample dist.xml (which used tcpping) for our cluster:

<infinispan
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="urn:infinispan:config:10.1 http://www.infinispan.org/schemas/infinispan-config-10.1.xsd
                          urn:org:jgroups http://www.jgroups.org/schema/jgroups-4.0.xsd"
      xmlns="urn:infinispan:config:10.1"
      xmlns:ispn="urn:infinispan:config:10.1">
   <jgroups>
       <stack name="tcpping" extends="tcp">
           <MPING ispn:stack.combine="REMOVE" xmlns="urn:org:jgroups"/>
           <TCPPING async_discovery="true"
                    initial_hosts="${initial_hosts:127.0.0.1[7800],127.0.0.1[7801]}"
                    port_range="0" ispn:stack.combine="INSERT_AFTER" ispn:stack.position="TCP" xmlns="urn:org:jgroups"/>
       </stack>
   </jgroups>

   <cache-container name="test" default-cache="dist">
       <transport cluster="test" stack="tcpping"/>
      <distributed-cache name="dist">
         <memory>
            <object size="21000"/>
         </memory>
      </distributed-cache>
   </cache-container>
</infinispan>