Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

Explore Java 17 language features with Quarkus

December 14, 2021
Ana-Maria Mihalceanu
Related topics:
ContainersJavaKubernetesOperators
Related products:
Red Hat build of Quarkus

    Quarkus is a Kubernetes-native Java framework made for Java virtual machines (JVMs) and native compilation, optimizing Java for containers. Java 17, the latest long-term support release of standard Java, became generally available on September 14, 2021. If you'd like to experiment to see how Quarkus works with the new language features that have rolled out since Java 11, this article offers a technical preview to help you get started.

    Set up your development environment

    In order to try the examples in this article, you will need:

    • An IDE.
    • JDK 17+ installed with JAVA_HOME configured appropriately.
    • Apache Maven 3.8.1+.

    To create a native executable, you will also need:

    • GraalVM version 21.3.0 installed and configured appropriately. Be sure to install support for Java 17.

    • A working container runtime, such as Podman or any other OCI-compliant tool.

    All the code for this sample project is available on GitHub.

    Bootstrap the Quarkus project

    To understand how several new Java features work with Quarkus, you will implement sample-app. This simple application exposes its own REST API based on data consumed from the Bored API, a public-facing service that suggests hobby activities you can pursue.

    The easiest way to create this new Quarkus project is to open a terminal and run the following command:

    mvn "io.quarkus:quarkus-maven-plugin:create" \
      -DprojectGroupId="com.redhat.developers" \
      -DprojectArtifactId="sample-app" \
      -DprojectVersion="1.0-SNAPSHOT" \
      -DclassName="HobbyResource" \
      -Dpath="actions"

    If you are a Windows user, simply inline the command as follows:

    mvn "io.quarkus:quarkus-maven-plugin:create" -DprojectGroupId="com.redhat.developers" -DprojectArtifactId="sample-app" -DprojectVersion="1.0-SNAPSHOT" -DclassName="HobbyResource" -Dpath="actions"

    The generated project contains an endpoint, HobbyResource. To finish the setup, go inside the sample-app directory. Let's add some Quarkus extensions by running:

    mvn quarkus:add-extension -Dextensions="quarkus-resteasy-jsonb, quarkus-container-image-jib, quarkus-rest-client,quarkus-smallrye-fault-tolerance, quarkus-smallrye-openapi"

    The project is now using the following Quarkus extensions:

    • quarkus-resteasy-jsonb to create JSON REST services using JSON-B serialization.
    • quarkus-smallrye-openapi to document the exposed API.
    • quarkus-rest-client to consume REST services exposed via the Bored API.
    • quarkus-smallrye-fault-tolerance to make the application resilient.
    • quarkus-container-image-jib to generate container images.

    Use sealed classes for API models

    Sometimes, you want to retain only certain details from data consumed from an external API before exposing those further to end users. It's useful in such cases to tailor a data model as a class hierarchy and serve only what is necessary for further consumption. sample-app does this with a class hierarchy that handles the details of activities consumed from the Bored API, as illustrated in Figure 1.

    Illustration of the class hierarchy for the sample application.
    License under MIT License.
    Figure 1. Class hierarchy for the sample application.

    The BasicHobby class contains fundamental details about an activity and restricts inheritance only to the PricedHobby class. BasicHobby can therefore be declared as a sealed class that permits inheritance to PricedHobby:

    public sealed class BasicHobby permits PricedHobby {
    
        public String key;
        public String activity;
        public String type;
        public int participants;
    
        @JsonbCreator
        public static BasicHobby empty() {
            return new BasicHobby();
        }
    }

    Furthermore, PricedHobby should also be a sealed class, as it contains all the basic details about an activity and its price, but with inheritance restricted to CompleteHobby:

    public sealed class PricedHobby extends BasicHobby permits CompleteHobby {
        public String randomId = UUID.randomUUID()
                .toString().substring(0, 4);
        public double price;
    
        @JsonbCreator
        public static PricedHobby empty() {
            return new PricedHobby();
        }
    }

    If CompleteHobby should not allow any further inheritance, the final keyword should precede it. For the moment, because CompleteHobby does not have any subclasses but the API model can evolve, you can use the non-sealed keyword:

    public non-sealed class CompleteHobby extends PricedHobby {
        public String link;
        public double accessibility;
    }

    With this model now in place, you can create the com.redhat.developers.ActivityService interface used to consume data from the Bored API. This interface should be annotated with @RegisterRestClient and contain only the last part of the path:

    @RegisterRestClient
    @Path("/api/activity")
    public interface ActivityService {
    
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        BasicHobby getActivityByType(@QueryParam("type") String type);
    
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        PricedHobby getActivity();
    
    
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        CompleteHobby getActivityByAccessibility(@QueryParam("minaccessibility") double minaccessibility, @QueryParam("maxaccessibility") double maxaccessibility);
    
    }

    Configure the URL in src/main/resources/application.properties:

    com.redhat.developers.ActivityService/mp-rest/url=https://www.boredapi.com
    

    Control API response using yield in switch expressions

    Occasionally, API endpoint implementations can return different response codes based on the parameters received. For instance, consider a scenario where the sample-app API does not support searching for an activity by type, except recreational and drawing activities. However, the drawing activity type has no content in the Bored API. In case the consumed API supports other activity types, the response should be 501 Not Implemented status, or else it will return that the service is not available.

    This scenario can be handled in a method implementation by using a switch expression that evaluates activity type:

    @Path("actions")
    public class HobbyResource {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(HobbyResource.class);
    
    
        @RestClient
        @Inject
        ActivityService service;
    
    
        @GET
        @Path("{type}")
        @Produces(MediaType.APPLICATION_JSON)
        public Response getHobbyByType(@PathParam("type") String type) {
           return switch(type){
               case "recreational" -> Response.status(OK)
                            .entity(service.getActivityByType(type)).build();
               case "drawing" -> Response.status(NO_CONTENT)
                           .entity(BasicHobby.empty()).build();
               default -> {
                   BasicHobby hobby = service.getActivityByType(type);
                   yield ((hobby.participants > 0) ? Response.status(NOT_IMPLEMENTED).build()
                           : invokeServiceUnavailable(type));
               }
            };
        }
    
    
        private Response invokeServiceUnavailable(String type) {
            LOGGER.debug(String.format("Type specified is not supported %s", type));
            return Response.status(SERVICE_UNAVAILABLE).entity(BasicHobby.empty()).build();
        }
    }

    For drawing and recreational cases we know the target type, so a single expression is used to the right of the case ->.  As the default scenario requires a block of code, a yield statement is used to yield a value, which becomes the value of the enclosing switch expression. Since Java 13, it's been possible to use a yield statement in place of a break statement with a value.

    Using records when implementing fault tolerance

    When you're consuming an external API, it's always good to have an alternative solution to avoid implementing a single point of failure. To see how this can work, introduce an error in the example implementation by adding an s to the end of the URL for the consumed service in src/main/resources/application.properties:

    com.redhat.developers.ActivityService/mp-rest/url=https://www.boredapis.com
    

    If you were to invoke any of the endpoints after making this change, they would throw an UnknownHostException, as nothing is currently hosted at boredapis.com. In order to prevent continuous invocation of a failed endpoint, you must annotate the endpoints with @CircuitBreaker, @Timeout, and/or @Retry:

    @RegisterRestClient
    @Path("/api/activity")
    public interface ActivityService {
    
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        @CircuitBreaker(failureRatio=0.75, delay = 1000 )
        @Timeout(150)
        @Retry(maxRetries = 4, delay = 100)
        BasicHobby getActivityByType(@QueryParam("type") String type);
    }

    The failure can be handled further using the @Fallback annotation. You could even offer a response while the API is unavailable by implementing a custom handler class:

    @RegisterRestClient
    @Path("/api/activity")
    public interface ActivityService {
    
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        @CircuitBreaker(failureRatio=0.75, delay = 1000 )
        @Timeout(150)
        @Retry(maxRetries = 4, delay = 100)
        @Fallback(DefaultBasicHobby.class)
        BasicHobby getActivityByType(@QueryParam("type") String type);
    
    }
    

    If you were using Java 8 or 11, DefaultBasicHobby would have been a public static class or an inner class of the ActivityService interface. But with Java 17, we can use records to implement DefaultBasicHobby inside ActivityService:

       record DefaultBasicHobby() implements FallbackHandler<BasicHobby> {
            @Override
            public BasicHobby handle(ExecutionContext executionContext) {
                return BasicHobby.empty();
            }
        }
    

    Generate container images using Jib

    When you created the sample-app project using the Maven plugin, Dockerfiles were also generated in src/main/docker. However, those were tailored to create container images that include JDK 11. The easiest way to generate a container image that includes JDK 17 without rewriting the Dockerfiles is by using quarkus-container-image-jib. This extension builds container images and is powered by Jib.

    If you wanted to generate a container image build and push it to an image registry, you could try the following configuration:

    quarkus.container-image.builder=jib
    
    #base image to be used when a container image is being produced 
    #for the jar build
    quarkus.jib.base-jvm-image=openjdk:17-jdk-alpine
    
    
    #registry and image details
    quarkus.container-image.registry=quay.io
    quarkus.container-image.group=anasandbox
    quarkus.container-image.tag=jvm-1.0
    
    #automatically pushes the image when packaging the app
    quarkus.container-image.push=true
    

    Packaging the application would produce the container image and push it to the registry:

    ./mvnw clean package

    But Quarkus is a Kubernetes-native Java framework, so you can also compile to native and build a native image. The native executable for our application will contain the application code, all required libraries, Java APIs, and a reduced version of a VM.

    For the native image build, replace the quarkus.jib.base-jvm-image configuration with quarkus.jib.base-native-image:

    quarkus.container-image.builder=jib
    
    #base image to be used when a container image is being produced 
    #for the native build
    quarkus.jib.base-native-image=registry.access.redhat.com/ubi8/ubi-minimal:8.5
    
    
    #registry and image details
    quarkus.container-image.registry=quay.io
    quarkus.container-image.group=anasandbox
    quarkus.container-image.tag=native-1.0
    
    #automatically pushes the image when packaging the app
    quarkus.container-image.push=true

    If your operating system is not based on Linux, when packaging to native you should also specify the docker image to use for the local build:

    ./mvnw package -Pnative \
    -Dquarkus.native.container-build=true \
    -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:21.3-java17

    This step is necessary to produce a build for a Linux binary. Without it, you'll get the following error when packaging or running the container:

    The native binary produced by the build is not a Linux binary and therefore cannot be used in a Linux container image.
    

    Conclusion

    Upgrading an application to JDK 17 is just the beginning. You can speed up your integration checks when upgrading by using Quarkus Dev Services to automatically provision services in development and test mode. Moreover, when you develop with Quarkus Continuous Testing enabled, you will be able to work while tests run immediately after code changes have been saved.

    The best way to try Quarkus with Java 17 is by using them together in different scenarios. If you are interested in Quarkus examples with other Java 11+ language features or learning how to tailor smaller container images, please leave a comment below.

    Last updated: October 6, 2022

    Related Posts

    • Quarkus for Spring developers: Kubernetes-native design patterns

    • Test-driven development with Quarkus

    • Modernizing Enterprise Java: A cloud native guide for developers

    • Deploy a Java application using Helm, Part 1

    • Boost throughput with RESTEasy Reactive in Quarkus 2.2

    Recent Posts

    • Protect data offloaded to GPU-accelerated environments with OpenShift sandboxed containers

    • Case study: Measuring energy efficiency on the x64 platform

    • How to prevent AI inference stack silent failures

    • Preventing GPU waste: A guide to JIT checkpointing with Kubeflow Trainer on OpenShift AI

    • How to manage TLS certificates used by OpenShift GitOps operator

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Chat Support

    Please log in with your Red Hat account to access chat support.