DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Java
  4. Dockerized App With Java Module System, Helidon, and Alpine-Based JDK 12 EA

Dockerized App With Java Module System, Helidon, and Alpine-Based JDK 12 EA

A long name for a short and sweet tutorial on Java's module system.

Uday Tatiraju user avatar by
Uday Tatiraju
·
Jan. 10, 19 · Tutorial
Like (8)
Save
Tweet
Share
8.03K Views

Join the DZone community and get the full member experience.

Join For Free

As you may know, JDK 9 introduced a native module system to design and build applications from a host of individual, independent modules that communicate with each other over well-defined public interfaces. Some of the advantages of using the Java module system are:

  • Strong encapsulation that promotes maintainability since the implementation details are hidden.
  • Reliable configuration to allow modules to declare their dependencies and address some of the issues with the traditional classpath mechanism.
  • Ability to build custom runtime images specific to an application.
  • Improved platform integrity and better performance through faster startup time and reduced memory footprint.
  • Indirect improvement in security due to strong encapsulation and a potential to reduce the platform and application’s attack surface.

In terms of container integration and support, JDK releases 10 and 11 made improvements in the JVM to ensure that the memory and CPU constraints set on a container are adhered to by the JVM. Not only that, JDK 12 early-access release now includes an Alpine Linux-based binary that uses the musl C library. The biggest advantage of using the Alpine Linux-based binary is that you can now build smaller Docker images to run your Java applications. Smaller image sizes, in turn, means we get benefits like faster downloads, faster startups, and reduced attack surface for security exploits.

Let’s build a sample application to take a look at some of these great features provided by the JDK. The application is a simple static image server built using Helidon and deployed as a Docker container. We’ll use Gradle as the build tool.

We begin by defining a Gradle build script to set up the Java module system and our app’s dependencies:

plugins {
    id "java-library"
    id "com.zyxist.chainsaw" version "0.3.1"
}
repositories {
    mavenCentral()
}
sourceCompatibility = "11"
dependencies {
    compile "io.helidon.webserver:helidon-webserver:0.10.5"
    compile "io.helidon.webserver:helidon-webserver-netty:0.10.5"
}


Since we are using the native Java module system, we will create a “module-info” class to define our module:

module example.imageserver {
    exports example.imageserver;

    requires java.logging;
    requires io.helidon.webserver;
}


Next, we create a static image server using Helidon’s built-in static content handler:

var contentSupport = StaticContentSupport
        .create(Paths.get("/app/images"));

var routing = Routing.builder()
        .register("/images", contentSupport)
        .build();

var config = ServerConfiguration.builder()
        .port(8080)
        .build();

WebServer.create(config, routing).start();


Yes, that is code written in Java. And that is all it takes to create a static web server. Now let’s turn our attention to building a Docker image. We will make use of the palantir gradle plugin and configure it to build our Docker image. Here’s the relevant Gradle build file snippet:

plugins {
    ...
    id 'com.palantir.docker' version "0.20.1"
}
...
docker {
    name "image-server:${version}"
    files configurations.compileClasspath, "${jar.archivePath}"
    copySpec.into("app")
}


Next, we define the “Dockerfile” that uses the multi-stage build process to create the Docker image for our application:

FROM alpine:latest as build

# Check the JDK 12 EA downloads link to get the latest version
RUN mkdir -p /opt/jdk \
    && wget -q "https://download.java.net/java/early_access/alpine/20/binaries/openjdk-12-ea+20_linux-x64-musl_bin.tar.gz" \
    && tar -xzf "openjdk-12-ea+20_linux-x64-musl_bin.tar.gz" -C /opt/jdk

RUN ["/opt/jdk/jdk-12/bin/jlink", \
     "--compress=2", \
     "--strip-debug", \
     "--no-header-files", \
     "--no-man-pages", \
     "--module-path", "/opt/jdk/jdk-12/jmods", \
     "--add-modules", "java.base,java.logging,jdk.unsupported", \
     "--output", "/custom-jre"]

FROM alpine:latest
COPY --from=build /custom-jre /opt/jdk/
ADD app /app

CMD ["/opt/jdk/bin/java", \
     "--upgrade-module-path", "/app", \
     "-m", "examples.imageserver/examples.imageserver.Server"]


Notice how we used the first stage of the Docker build process to download the Alpine Linux-based JDK and create a custom JRE using jlink. For the custom JRE, apart from adding the “java.base” module and the “java.logging” module, we also added the “jdk.unsupported” module to allow netty (that powers Helidon) access to internal JDK classes.

The second stage of the Docker build will start with Alpine Linux as the base image (roughly around 4.5 MB). We then add the custom JRE that was built in the first stage along with our application modules. The last step is to add the command to run the application. Equipped with these files and configurations, it’s time to build our app and package it as a docker image. Let’s ask Gradle to do that for us:

gradlew clean build docker


The above command will build a Docker image that just contains our custom JRE along with the application jars. If you now run docker images , you will see that our Docker image is a measly 58.6 MB in size. Isn’t that wonderful? We can now run our application as a Docker container:

docker run -d -p 8080:8080 imageserver


Using a browser, navigate to http://localhost:8080/images to see our Ddockerized application in action.

You can head over to Github to clone and play with the sample project.

Java (programming language) Java Development Kit app

Published at DZone with permission of Uday Tatiraju, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Image Classification With DCNNs
  • Secrets Management
  • What Is Policy-as-Code? An Introduction to Open Policy Agent
  • Integration: Data, Security, Challenges, and Best Solutions

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: