Quarkus: Get started with Kubernetes-native Java

Red Hat’s Quarkus is a full-stack, open-source Java framework designed for containers and cloud environments. See why it’s a worthy alternative to Spring.

Quarkus is a full-stack, open-source Java framework launched in 2019 by Red Hat. Quarkus is an alternative to Spring (although it can also be integrated with Spring), with some interesting features all to itself.

Quarkus is designed from the ground up to be “cloud-native,” meaning it’s tuned for Kubernetes, serverless platforms, and a variety of libraries like Apache Kafka (for stream processing) and JAX-RS (for RESTful web services). Quarkus also sports a full CDI (contexts and dependency injection) engine and Reactive programming support.

Read on for a hands-on introduction to Quarkus.

Install the Quarkus CLI

Quarkus supports Maven and Gradle as build tool wrappers, but also ships with a command-line interface (CLI) tool. We’ll begin by installing the CLI via the JBang tool. From the command line, run the code in Listing 1.

Listing 1. Installing the Quarkus CLI

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

// PowerShell:
iex "& { $(iwr https://ps.jbang.dev) } app install --fresh --force quarkus@quarkusio"

Once the command completes, typing quarkus -version should return a result.

Create a Quarkus app

Create a new app by typing quarkus create app com.infoworld:my-quarkus:1.0, where com.infoworld is the group ID, my-quarkus is the artifact ID, and 1.0 is the version number.

The command-line tool has many options that you can use to configure things. You can get a look at these options by typing quarkus --help. You can get help with a specific command by typing quarkus create app --help. Notice you can define things like which build tool wrapper to use.

Running the Quarkus app

Now move into the new my-quarkus directory and type quarkus dev. Quarkus dev mode represents a neat approach. It supports hot code loading and allows you to run tests at will without stopping your container. This is why you are given some command options after the dev container spins up with the prompt:

Tests paused Press [r] to resume testing, [o] Toggle test output, [h] for more options>

This means you can make code changes, including adding tests, and then run the tests with r. You can also change logging levels with h or stop the container with q.

Running Quarkus dev mode on a remote machine

By default, Quarkus dev mode only listens on localhost. If you want to listen on all networks, you can add the host param:

quarkus dev -Dquarkus.http.host=0.0.0.0

To build the app for production, you use the quarkus build command.

While the app is running in dev mode, visit localhost:8080, and you will see the welcome screen, similar to Figure 1.

Figure 1. Quarkus welcome

quarkus welcome IDG

Exploring the Quarkus project layout

Inside the new app directory is the src directory, following the typical Maven layout, with test for tests and main for the app files. Inside /src/main are three directories: /src/main/resources, where your static files live (including the HTML pages that drive your pages like src/main/resources/META-INF/resources/index.html); /src/main/java, where your back-end code and middleware code live; and /src/main/docker, where Quarkus has generated default Dockerfiles for you (including Dockerfile.native for running without a JVM).

Any changes you make to the app will be reflected when the browser is refreshed. For example, you can open the src/main/java/com/infoworld/GreetingResource.java file and see a JAX-RS endpoint. If you modify the message and reload localhost:8080/hello, you’ll find your changes reflected.

Listing 2. The /hello REST endpoint

package com.infoworld;

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 "Welcome to the machine";
    }
}

Quarkus extensions

Quarkus has many capabilities that are delivered via extensions that can be added with a command. To get a sense of what Quarkus supports, take a look at this Git repo, which contains several dozen examples for using Quarkus with the likes of JPA, Kafka, MongoDB, Amazon S3, and Knative. These examples are also good for using as starting points when developing a Quarkus app with these technologies (hence the name quickstarts).

You can see which extensions are installed in your app by running quarkus ext ls. You’ll see that right now only quarkus-resteasy is installed. You can get a list of available installable modules with quarkus ext ls -i.

Installation is performed with the quarkus ext add command.

Quarkus CDI dependency injection

Quarkus ships with a custom-built dependency injection engine, called ArC. ArC is a partial implementation of the CDI spec, and also has its own special features. Overall, the ArC system is simpler and easier to understand than CDI.

Let’s see how to add a service class for use by the RESTful resource. Begin by creating a new file at /src/main/java/com/infoworld/service/MyService.java. Add the code seen in Listing 3.

Listing 3. The MyService class

package com.infoworld.service;

import javax.enterprise.context.ApplicationScoped;
import java.util.Random;

@ApplicationScoped
public class MyService {
    public Integer getRandom(){
      Random random = new Random();
      return random.nextInt(100);
    }
}

MyService is a very simple class with a single method, getRandom(), which returns a random integer. It is annotated with the CDI standard annotation, @ApplicationScoped, that makes it available to the DI system. (Learn more about CDI scopes and contexts here.) Note that Quarkus CDI is included in your project because the RESTEasy extension uses it.

Now open up the /src/main/java/com/infoworld/GreetingResource.java class and modify it as seen in Listing 4.

Listing 4. GreetingResource using MyService

package com.infoworld;

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

import javax.inject.Inject;
import com.infoworld.service.MyService;

@Path("/hello")
public class GreetingResource {
    @Inject
    MyService myService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Welcome to the machine.  Your number is: " + myService.getRandom();
    }
}

The key element here is the @Inject annotation, which causes the CDI engine to fulfill the myService reference with the resource bean we created in Listing 4. This is then used to generate the random number for the /hello path.

Fix the GreetingResource test

Now if you return to the command line where Quarkus dev is running, and type r to rerun tests, you’ll see that the test case for the RESTEasy endpoint fails. Let’s fix that. Open test/java/com/infoworld/GreetingResourceTest.java. Notice this class uses several different testing libraries already helpfully provided by Quarkus, including the RestAssured library, which makes for easy testing of RESTful endpoints.

Modify GreetingResourceTest as seen in Listing 5.

Listing 5. Updated GreetingResourceTest

package com.infoworld;

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

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

@QuarkusTest
public class GreetingResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/hello")
          .then()
             .statusCode(200)
             .body(containsString("machine"));  // changed
    }
}

As noted in the comments, only two lines are changed. You simply check for the existence of the word “machine” in the response body. Now if you run the tests with the r command, they will pass.

Add the REST client extensions

Let’s go a little deeper and make a call to an external REST API (the Star Wars API, aka SWAPI) in the service class. Begin by adding the quarkus-rest-api client by running quarkus ext add quarkus-rest-client. Note that you can do this in another window while the app is still running and Quarkus will apply the extension. Pretty impressive.

We’ll also leverage the rest-client-jackson extension to make modeling the data simple. Type quarkus ext add rest-client-jackson.

Model class

Create a model class in a new directory: src/main/java/com/infoworld/client/Person.java. This will be a very simple data transfer object that Jackson will populate for us, as seen in Listing 6.

Listing 6. Person model class

package com.infoworld.client;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    public String name;
}

Notice we use the @JsonIgnoreProperties so no errors are thrown when fields exist on the source JSON that don’t exist on the model object.

In the same directory, create a new service class that will hit the SWAPI endpoint: /src/main/java/com/infoworld/client/SwapiClient.java. See the contents of that file in Listing 7.

Listing 7. SwapiClient.java

package com.infoworld.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.Set;

@RegisterRestClient
public interface SwapiClient {
    @GET
    @Path("/people/{id}")
    @Produces("application/json")
    Person getById(@PathParam Integer id);
}

Notice the interface is registered as a rest client with the @RegisterRestClient annotation. The method getById is also annotated to define it as hitting a GET HTTP Method found as "/people/{id}", where the id is going to be provided by the call to the getById(@PathParam Integer id) method. We are here defining with the same annotations that define a restful endpoint, a restful client.

But where is the root of the endpoint defined? Open up src/main/resources/application.properties and add the two lines seen in Listing 8.

Listing 8. Defining the REST client properties in application.properties

com.infoworld.client.SwapiClient/mp-rest/url=https://swapi.dev/api
com.infoworld.client.SwapiClient/mp-rest/scope=javax.inject.Singleton

More info on these properties and how they are wired into the application is available here.

Now update the GreetingResource to use the new client service, as in Listing 9.

Listing 9. GreetingResource using the REST client

package com.infoworld;

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

import javax.inject.Inject;
import com.infoworld.service.MyService;
import com.infoworld.client.SwapiClient;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

@Path("/hello")
public class GreetingResource {
@Inject
MyService myService;

@Inject
@RestClient
SwapiClient swapiClient;

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello, your Star Wars character is: " + swapiClient.getById(myService.getRandom()).name;
}
}

Now we are injecting the rest client into the swapiClient variable, and calling the getById method with a random ID from the random number service you created earlier. Now you’ll get a response from localhost:8080/hello that goes something like this: 

Hello, your Star Wars character is: Ric Olié

Cloud-native Java

This was a whirlwind tour of some of the power of Quarkus, but only the beginning. Quarkus is definitely a valid alternative to Spring, and continues to see active development, with new and useful features being added frequently. The next time you reach for a Java framework, consider Quarkus.

All of the code for this tutorial is available here.

Copyright © 2021 IDG Communications, Inc.

How to choose a low-code development platform