Getting started with QuarkusIO

Welcome to QuarkusIO, the lastest project for supersonic Java! Quarkus features a Cloud Native, Container first, Microservice ready framework for writing Java applications based on the standards and frameworks you are using today (Hibernate, RESTEasy, Camel, Vert.X etc).

What does it mean ?

  • Container First: Minimal footprint Java applications optimal for running in containers
  • Cloud Native: Embraces 12 factor architecture in environments like Kubernetes.
  • Microservice First: Brings lightning fast startup time and code turn around to Java apps

Here is a bird’s eye view of the Project:

As you can see, one of the most interesting aspect of quarkus is that you can build native executables using GraalVM. That makes Quarkus applications ideal for containers and serverless workloads. In this first tutorial we will learn how to build a Quarkus REST application using the Maven tooling available and add a minimal of compexity to it. There are two main major versions for Quarkus applications and each one has different requirements. Let’s start from the latest version:

Building your first Quarkus 2.x application

JDK 11 is the minimal version to use Quarkus 2.0. Quarkus 2.0 applications can be bootstrapped either using Quarkus CLI or its Maven plugin. Running Quarkus with Maven plugin is covered in the next paragraph.

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.

Let’s use it to 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:

tree src
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

The most interesting items are:

  • The GreetingResource.java containing the Hello REST Endpoint
  • Several Dockerfile : The most commonly used ones are Dockerfile.jvm to build the application as a Container using a Java image. Dockerfile.native to build a native image of the application.
  • An index.html file to remind you that you can add static files (HTML, images, Javascript) in that folder
  • Two Test classes added under the folder “test” to test the application with Java or as native executable.
  • A file named application.properties which is the main configuration file in Quarkus

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:

quarkus 2.0 tutorial getting started

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>

Building your first Quarkus 1.x application

Requirements to build a Quarkus Java Applications:

  • JDK 8 or 11+ installed with JAVA_HOME configured appropriately
  • Apache Maven 3.6.2+

Quarkus applications can be quickly bootstrapped using the Maven or Gradle plugin available. The plugin will generate a minimal project structure with a sample REST Endpoint and the Quarkus’s Maven dependencies included in the configuration file. Let’s assume you are using Maven:

mvn io.quarkus:quarkus-maven-plugin:1.4.1.Final:create -DprojectGroupId=com.sample -DprojectArtifactId=hello-quarkus -DclassName="com.sample.QuarkusEndpoint" -Dpath="/hello"

The following project structure will be created:

hello-quarkus/
β”œβ”€β”€ mvnw
β”œβ”€β”€ mvnw.cmd
β”œβ”€β”€ pom.xml
└── src
    β”œβ”€β”€ main
    β”‚   β”œβ”€β”€ docker
    β”‚   β”‚   β”œβ”€β”€ Dockerfile.jvm
    β”‚   β”‚   └── Dockerfile.native
    β”‚   β”œβ”€β”€ java
    β”‚   β”‚   └── com
    β”‚   β”‚       └── sample
    β”‚   β”‚           └── QuarkusEndpoint.java
    β”‚   └── resources
    β”‚       β”œβ”€β”€ application.properties
    β”‚       └── META-INF
    β”‚           └── resources
    β”‚               └── index.html
    └── test
        └── java
            └── com
                └── sample
                    β”œβ”€β”€ NativeQuarkusEndpointIT.java
                    └── QuarkusEndpointTest.java

The most interesting items are:

  • The QuarkusEndpoint.java containing the Hello REST Endpoint
  • Two Dockerfile : Dockerfile.jvm to build the application as a Container using a Java image. Dockerfile.native to build a native image of the application.
  • An index.html file to remind you that you can add static files (HTML, images, Javascript) in that folder
  • Two Test classes added under the folder “test” to test the application with Java or as native executable.
  • A file named application.properties which is the main configuration file in Quarkus

Here is the quintessential REST Endpoint, which returns a text string upon invocation of the “hello()” GET Resource.

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 QuarkusEndpoint {

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

The pom.xml file, reflects the libraries needed to bootstrap the project. Here is the dependencies section of it:

  <properties>
    <compiler-plugin.version>3.8.1</compiler-plugin.version>
    <maven.compiler.parameters>true</maven.compiler.parameters>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <quarkus-plugin.version>1.0.0.Final</quarkus-plugin.version>
    <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
    <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
    <quarkus.platform.version>1.0.0.Final</quarkus.platform.version>
    <surefire-plugin.version>2.22.1</surefire-plugin.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>${quarkus.platform.group-id}</groupId>
        <artifactId>${quarkus.platform.artifact-id}</artifactId>
        <version>${quarkus.platform.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>
  </dependencies>

You can build and run your project from the root folder with:

mvn clean compile quarkus:dev

The application will bootstrap Quarkus in a snap:

[INFO] Running com.sample.QuarkusEndpointTest
2019-03-14 16:46:19,743 INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
2019-03-14 16:46:20,292 INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 549ms
2019-03-14 16:46:20,610 INFO  [io.quarkus] (main) Quarkus 1.0.0.Final started in 0.294s. Listening on: http://127.0.0.1:8081

You can test it with:

curl http://localhost:8080/hello

That will return the “Hello” String

Debugging and Hot Deploying with Quarkus

As you can see, we have launched Quarkus with quarkus:dev which runs Quarkus in development mode. This enables hot deployment with background compilation, which means that when you modify your Java files your resource files and refresh your browser these changes will automatically take effect. This works too for resource files like the configuration property file. Refreshing the browser triggers a scan of the workspace, and if any changes are detected the Java files are recompiled and the application is redeployed; your request is then serviced by the redeployed application. If there are any issues with compilation or deployment an error page will let you know.

Also, in that mode, Quarkus will listen for a debugger on port 5005. If your want to wait for the debugger to attach before running you can pass -Ddebug on the command line. If you don’t want the debugger at all you can use -Ddebug=false.

Building a more complex application

We will now learn how to build a slightly more complex Quarkus application which still uses a REST Endpoint but has a minimal UI to insert data in memory.

You can re-use the same project you have just built, or create a new one from scratch. Start by adding a Java Bean to be used as Domain class:

package com.sample;

import java.util.Objects;

public class Person {

    String name;
    String surname;

    public Person( ) {  }

    public String getName() {
        return name;
    }
    public String getSurname() {  return surname;  }
    public void setName(String name) {
        this.name = name;
    }
    public void setSurname(String surname) {
        this.surname = surname;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(surname, person.surname);
    }

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

Now a REST Endpoint to save this object in a Collection:

package com.sample;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Set;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/persons")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class RESTEndpoint {

    private Set<Person> persons = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));

    @GET
    public Set<Person> list() {
        return persons;
    }

    @POST
    public Set<Person> add(Person person) {
        System.out.println("Saving: " +person);
        persons.add(person);
        return persons;
    }

}

Basically, we have added a GET method to retrieve the list of Person, and a POST method to save a new Person in the Set.

As the above class can Produce and Consume JSON, we need to include also the quarkus-resteasy-jsonb artifact to our dependencies:

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

As it is, our application it is ready to be tested:

mvn clean compile quarkus:dev

You can add a new entry with cURL, passing as data the JSON to be consumed:

curl -d '{"name":"john", "surname":"black"}' -H "Content-Type: application/json" -X POST http://127.0.0.1:8080/persons

We can check that the data is available, with the corresponding GET request:

curl http://127.0.0.1:8080/persons
[{"name":"john","surname":"black"}]

Adding an UI to our Quarkus project

Finally, we can show how we can add Web application pages to our project. In order to do that, we need to add HTML pages under the following path:

β”‚   └── resources
β”‚       └── META-INF
β”‚           └── resources

For example, we will add an HTML page using AngularJS to display the data in Tabular format, with a Form to add new items via POST:

<!doctype html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Quarkus REST service</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/wingcss/0.1.8/wing.min.css"/>
    <!-- Load AngularJS -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
    <script type="text/javascript">
      var app = angular.module("PersonManagement", []);

      //Controller Part
      app.controller("PersonManagementController", function ($scope, $http) {

        //Initialize page with empty data
        $scope.persons = [];

        $scope.form = {
          name: "",
          surname: ""
        };

        //Now load the data from server
        _refreshPageData();

        //HTTP POST methods for add data
        $scope.add = function () {
          var data = { "name": $scope.form.name, "surname": $scope.form.surname };

          $http({
            method: "POST",
            url: '/persons',
            data: angular.toJson(data),
            headers: {
              'Content-Type': 'application/json'
            }
          }).then(_success, _error);
        };


        //HTTP GET- get all persons collection
        function _refreshPageData() {
          $http({
            method: 'GET',
            url: '/persons'
          }).then(function successCallback(response) {
            $scope.persons = response.data;
          }, function errorCallback(response) {
            console.log(response.statusText);
          });
        }

        function _success(response) {
          _refreshPageData();
          _clearForm();
        }

        function _error(response) {
          alert(response.data.message || response.statusText);
        }

        //Clear the form
        function _clearForm() {
          $scope.form.name = "";
          $scope.form.surname = "";
        }
      });
    </script>
</head>
<body ng-app="PersonManagement" ng-controller="PersonManagementController">

<div class="container">
    <h1>Quarkus REST Service</h1>

    <form ng-submit="add()">
        <div class="row">
            <div class="col-6"><input type="text" placeholder="Name" ng-model="form.name" size="60"/></div>
        </div>
        <div class="row">
            <div class="col-6"><input type="text" placeholder="Surname" ng-model="form.surname" size="60"/></div>
        </div>
        <input type="submit" value="Save"/>
    </form>

    <h3>Person List</h3>
    <div class="row">
        <div class="col-4">Name</div>
        <div class="col-8">Surname</div>
    </div>
    <div class="row" ng-repeat="person in persons">
        <div class="col-4">{{ person.name }}</div>
        <div class="col-8">{{ person.surname }}</div>
    </div>
</div>

</body>
</html>

Again, run your application with:

mvn clean compile quarkus:dev

And see it in action at: http://localhost:8080

Quarkus io tutorial wildfly quarkus

Now experiment changing anything in the HTML page, for example the Heading contained in it. As the application is running in Development mode, you will see your changes without restarting your app!

Quarkus io tutorial wildfly quarkus

Conclusion

In this first tutorial about QuarkusIO we have covered the basic set up of an application and the tooling which can be used to bootstrap your projects. Continue learning QuarkusIO in this tutorial which shows how to create a native application: Building Container-ready native applications with Quarkus

Source code for this example: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/hello-quarkus

References: https://quarkus.io/