How to create Quarkus Command Mode applications

Quarkus is set of technologies to develop an entire Microservice architecture. The foundation of this architecture is typically an HTTP server, serving REST Endpoints. It is however also possible to create powerful Java scripts using Quarkus advanced sets of APIs. In this tutorial we will learn how to create standalone Quarkus applications with a bare simple main entry point.

Continue reading How to create Quarkus Command Mode applications

What’s new with Quarkus 2.0 and how to get started quickly

Quarkus 2.0 has been released! In this article, we will cover the highlights of the new major version and the requirements to get started or migrate to this exciting new version.

Quarkus 2.0 aims to bring ease of development at an even higher level. There are several improvements in the tooling area and the major release of some core frameworks (namely Vert.x and Microprofile) have been both upgraded to their latest version. Let’s start with the requirements.

Quarkus 2.0 requires JDK 11 as a minimal version. Also, if you are compiling your applications to native code, GraalVM 21.1 is the recommended version for Quarkus 2.0.

You can grabe GraalVM 21.1 from here: https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-21.1.0

Major framework improvements

Quarkus uses Vert.x as a toolkit for build­ing re­ac­tive ap­pli­ca­tions on the JVM. Quarkus 2.0 is based on Vert.x 4 which ex­tends the Vert.x 3 line with a set of new fea­tures such as improved user experience, simpler handling of Future Callbacks, Microservices monitoring, high-performance re­ac­tive SQL clients, SQL templating, simple Web Session storage on an ex­ter­nal back­end. We will discuss more in detail Vert.x 4 in a separate tutorial.

The other core framework used by Quarkus is Microprofile, which now targets version 4 of the MicroProfile specifications.

All the SmallRye components in Quarkus have been updated to the latest and greatest and, apart from implementing MicroProfile 4, they also come with additional new features.

Quarkus CLI

So far, if you wanted to create Quarkus applications from the Command Line, the best option available for a quick bootstrap was using Quarkus Maven plugin. Now you can install Quarkus CLI for a simpler project creation/execution/testing. Quarkus CLI relies on JBang which is a scripting tool which allows running Java application with minimal setup, without the need of having a project configuration.

More about jbang here: JBang: Create Java scripts like a pro

You can install Quarkus CLI on Linux, macOS, and Windows (using a tool like like cygwin or mingw) as follows:

curl -Ls https://sh.jbang.dev | bash -s - app install --fresh --force quarkus@quarkusio

Once installed, the tool “quarkus” will be in your PATH. Open a new command line and try:

quarkus --help
Usage: quarkus [-ehv] [--verbose] [COMMAND]
  -e, --errors    Display error messages.
  -h, --help      Show this help message and exit.
  -v, --version   Print version information and exit.
      --verbose   Verbose mode.

Continuous Testing

Another outstanding feature of Quarkus 2.0 is Continuous Testing. This feature allows you to run (and customize) your tests in development mode on-demand, using a keyboard menu. The advantage is that you can go through application development, live reload and application tests without stopping your services. This will give you instant feedback on your changes, without the need to restart your environment every time. In the following example, we will show in practice how Continuous Testing works.

Creating a Quarkus 2.0 application with the CLI

Creating a Quarkus 2.0 application is just one line away. For example, let’s create one named “demo” with the following Maven coordinates:

$ quarkus create app --group-id com.sample --artifact-id demo --version 1.0
-----------

applying codestarts...
  java
  maven
  quarkus
  config-properties
  dockerfiles
  maven-wrapper
  resteasy-codestart

-----------
[SUCCESS]   quarkus project has been successfully generated in:
--> /home/quarkus/demo

Fine, let’s see what we have got:

src
├── main
│   ├── docker
│   │   ├── Dockerfile.jvm
│   │   ├── Dockerfile.legacy-jar
│   │   ├── Dockerfile.native
│   │   └── Dockerfile.native-distroless
│   ├── java
│   │   └── com
│   │       └── sample
│   │           └── GreetingResource.java
│   └── resources
│       ├── application.properties
│       └── META-INF
│           └── resources
│               └── index.html
└── test
    └── java
        └── com
            └── sample
                ├── GreetingResourceTest.java
                └── NativeGreetingResourceIT.java

So, a Greeting application has been added, which uses a minimal development stack (CDI+REST and RESTAssured for testing). Here’s the Controller app:

package com.sample;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy";
    }
}

Now build your application with the “build” command:

$ quarkus build

Quarkus CLI can also run applications using the “dev” command:

$ quarkus dev

[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.sample:demo >---------------------------
[INFO] Building demo 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- quarkus-maven-plugin:2.0.0.Final:dev (default-cli) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/francesco/quarkus/demo/target/classes
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/francesco/quarkus/demo/src/test/resources
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/francesco/quarkus/demo/target/test-classes
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-07-04 12:35:09,464 INFO  [io.quarkus] (Quarkus Main Thread) demo 1.0 on JVM (powered by Quarkus 2.0.0.Final) started in 1.535s. Listening on: http://localhost:8080
2021-07-04 12:35:09,479 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-07-04 12:35:09,479 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, smallrye-context-propagation]

The only REST Endpoint is available through the “/hello” Path:

Besides that, you can check at the bottom of the Quarkus log, the following log emitted by Continuous Testing:

--
Tests paused, press [r] to resume, [h] for more options>

So, you can, at any time, execute a round of tests on your services. For example, press “r” to run tests:

All 1 tests are passing (0 skipped), 1 tests were run in 2191ms. Tests completed at 12:35:34.
Press [r] to re-run, [v] to view full results, [p] to pause, [h] for more options>

Besides that, hitting “h” you can see all the available options to customize your tests:

The following commands are available:
[r] - Re-run all tests
[f] - Re-run failed tests
[b] - Toggle 'broken only' mode, where only failing tests are run (disabled)
[v] - Print failures from the last test run
[o] - Toggle test output (disabled)
[p] - Pause tests
[i] - Toggle instrumentation based reload (disabled)
[l] - Toggle live reload (enabled)
[s] - Force live reload scan
[h] - Display this help
[q] - Quit

Adding extensions through Quarkus CLI

The default application we have created included just a minimal set of extensions. You can add new extensions (using the CLI) in two ways. At first, you can add as an extra parameter the list of extensions. For example, suppose you wanted to add the health and kubernetes extensions:

$ quarkus create app --group-id com.sample --artifact-id demo --version 1.0 health kubernetes
selected extensions: 
- io.quarkus:quarkus-kubernetes
- io.quarkus:quarkus-smallrye-health

If you want to add extensions in an existing project, use the “ext add” command, followed by the extension list:

$ quarkus ext add kubernetes health

To know the list of available extensions, use the “ext list command:

$ quarkus ext add kubernetes health

To filter through the list, besides the good old grep, you can also use the “-s” command, combined with the “–concise” option which provides the artifact name of an extension:

$ quarkus ext list --concise -s vertx
Eclipse Vert.x                                     quarkus-vertx

References: https://quarkus.io/guides/cli-tooling

Data Persistence with Quarkus and Hibernate Panache

In this article we will learn how to simplify Data Persistence with Quarkus, using Hibernate ORM Panache a library that stands on the top of Hibernate.

Bootstrap a Quarkus project with Panache

Let’s start creating a Quarkus project which includes the following extensions:

mvn io.quarkus:quarkus-maven-plugin:2.3.0.Final:create \
     -DprojectGroupId=com.mastertheboss \
     -DprojectArtifactId=panache-demo \
     -DclassName="com.mastertheboss.MyService" \
     -Dpath="/tickets" \
     -Dextensions="quarkus-hibernate-orm-panache,quarkus-jdbc-postgresql,quarkus-resteasy-jsonb"
If you prefer, you can use Quarkus CLI to create your projects. Read more here: What’s new with Quarkus 2.0 and how to get started quickly

As you can see, we have included a set of additional extensions that will be needed to create our application:

Adding extension io.quarkus:quarkus-jdbc-postgresql
Adding extension io.quarkus:quarkus-hibernate-orm-panache
Adding extension io.quarkus:quarkus-resteasy-jsonb

So, you should have the following dependencies in your project.

 <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-bom</artifactId>
        <version>${quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</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>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>

Hibernate Panache is an addition to Hibernate which makes faster to write the ORM layer for your applications. There are two strategies for plugging Panache in your Entity:

  • Extending the class io.quarkus.hibernate.orm.panache.PanacheEntity : This is the simplest option as you will get an ID field that is auto-generated.
  • Extending io.quarkus.hibernate.orm.panache.PanacheEntityBase : This option can be used if you require a custom ID strategy.

In the following Entity we will use the former strategy, that is extending the class io.quarkus.hibernate.orm.panache.PanacheEntity:

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;
    }
}

When extending the class io.quarkus.hibernate.orm.panache.PanacheEntity you will be able to use out of the box some benefits like an ID field that is auto-generated. You can still choose a custom ID strategy, by extending the PanacheEntityBase instead and handle the ID yourself.

Also, Panache uses public Class variables for your fields hence there is no need to write boilerplate getters and setters. You can directly refer to the field name. Let’s now build an example application which uses the above Entity class.

Next, edit the class com.mastertheboss.MyService to include the required methods to perform CRUD operations on our Entity:

import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.json.Json;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.jaxrs.PathParam;

import io.quarkus.panache.common.Sort;

@Path("tickets")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class MyService {

    @GET
    public List<Ticket> get() {
        return Ticket.listAll(Sort.by("name"));
    }

    @GET
    @Path("{id}")
    public Ticket getSingle(@PathParam Long id) {
        Ticket entity = Ticket.findById(id);
        if (entity == null) {
            throw new WebApplicationException("Ticket with id of " + id + " does not exist.", 404);
        }
        return entity;
    }

    @POST
    @Transactional
    public Response create(Ticket ticket) {
        if (ticket.id != null) {
            throw new WebApplicationException("Id was invalidly set on request.", 422);
        }

        ticket.persist();
        return Response.ok(ticket).status(201).build();
    }

    @PUT
    @Path("{id}")
    @Transactional
    public Ticket update(@PathParam Long id, Ticket ticket) {
        if (ticket.name == null) {
            throw new WebApplicationException("Ticket Name was not set on request.", 422);
        }

        Ticket entity = Ticket.findById(id);

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

        entity.name = ticket.name;

        return entity;
    }

    @DELETE
    @Path("{id}")
    @Transactional
    public Response delete(@PathParam Long id) {
        Ticket entity = Ticket.findById(id);
        if (entity == null) {
            throw new WebApplicationException("Ticket with id of " + id + " does not exist.", 404);
        }
        entity.delete();
        return Response.status(204).build();
    }

    @Provider
    public static class ErrorMapper implements ExceptionMapper<Exception> {

        @Override
        public Response toResponse(Exception exception) {
            int code = 500;
            if (exception instanceof WebApplicationException) {
                code = ((WebApplicationException) exception).getResponse().getStatus();
            }
            return Response.status(code)
                    .entity(Json.createObjectBuilder().add("error", exception.getMessage()).add("code", code).build())
                    .build();
        }

    }
}

As you can see, the above Class produces and consumers resoruces as JSON files. The Service will be available at the URI path “/tickets”.

In order to have some default Entities in the Database, we can include a file src/main/resources/import.sql

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')

Next, as we have included PostgreSQL dependencies, we will start a Docker instance of it:

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

Next, to connect to PostgreSQL, we will configure the file src/main/resources/application.properties to include the Connection Pool to reach the Database:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkusdb
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

Finally, we will be editing the MyServiceTest class to check that the three Entity have been inserted:

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

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

@QuarkusTest
public class MyServiceTest {

    @Test
    public void testListAllTickets() {
        given()
                .when().get("/tickets")
                .then()
                .statusCode(200)
                .body(
                        containsString("Phantom of the Opera"),
                        containsString("Chorus Line"),
                        containsString("Mamma mia")
                );

    }

}

Test your Project

You can compile/test the application with:

$ mvn install


2019-05-24 12:00:43,576 INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
2019-05-24 12:00:44,538 INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 962ms
Hibernate: 
    
    drop table if exists Ticket cascade
Hibernate: 
    
    drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start 1 increment 1
Hibernate: 
    
    create table Ticket (
       id int8 not null,
        name varchar(20),
        seat varchar(3),
        primary key (id)
    )
Hibernate: 
    
    alter table if exists Ticket 
       add constraint UK_18i99euw9b8fmairpxnxoj10j unique (name)
Hibernate: 
    
    alter table if exists Ticket 
       add constraint UK_r55ptntdg7v0d3249djuu1037 unique (seat)
Hibernate: 
    INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Phantom of the Opera','11A')
Hibernate: 
    INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Chorus Line','5B')
Hibernate: 
    INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Mamma mia','21A')

On the other hand, to execute the application as Java application:

$ mvn quarkus:dev

You can test the application with:

$ curl http://localhost:8080/tickets

[

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

]

Conclusion

In this tutorial we have covered how to build a basic CRUD application with Quarkus, Hibernate panache and PostgreSQL. You can refer to this tutorial to learn how to compile the same application to native code: Getting started with QuarkusIO

You can checkout the full source code for this tutorial at: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/panache-demo

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

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