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
Partner Zones AWS Cloud
by AWS Developer Relations
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
Partner Zones
AWS Cloud
by AWS Developer Relations

Trending

  • Building and Deploying Microservices With Spring Boot and Docker
  • JavaFX Goes Mobile
  • Reducing Network Latency and Improving Read Performance With CockroachDB and PolyScale.ai
  • Google Becomes A Java Developer's Best Friend: Instantiations Developer Tools Relaunched For Free
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. Dockerizing With a Custom JRE

Dockerizing With a Custom JRE

Taruvai Subramaniam user avatar by
Taruvai Subramaniam
CORE ·
Aug. 04, 20 · Tutorial
Like (5)
Save
Tweet
Share
7.89K Views

Join the DZone community and get the full member experience.

Join For Free

It is generally considered good-practice to have a small Docker image. While we can reduce the size of the base image of the operating system, for instance Alpine Linux which is only 5 MB, before Java 9 there was nothing we could do about the JRE. The lightest was the alpine JRE (openjdk:8-jre-alpine) coming in at about 107MB. This is because we are including classes that have nothing to do with the application, such as the applet or awt classes, even if your application is headless.

Starting in Java 9, the JRE was broken up into modules, so that it became possible to only include the modules used by the application. Java 9’s jLink enables us to create a custom, "just enough" JRE that only consists of the application classes and the modules it depends on.

In this paper, we will see how to dockerize a Java application built with jLink. We will illustrate with a running example built in Eclipse with Maven. Along the way, we will provide details on how to build a modular Java application in Eclipse. I will assume that you have a version of Eclipse later than Oxygen running on a version of Java at or later than Java 9.

First, create a Maven project with the quick start archetype and give it the GAV (com.tn.jlink;example). Create a package, com.tn.jlink.example. Right-click on the project and choose Configure -> create module-info.java and call the module com.tn.jlink. (See this for best practices in naming modules). Eclipse creates the file, and it already contains "exports com.tn.jlink.example". Add "requires java.logging" to that.

Plain Text
xxxxxxxxxx
1
 
1
module com.tn.jlink {
2
    exports com.tn.jlink.example;
3
    requires java.logging;
4
}


For simplicity, we will just use a built-in Java module

In that package, create this class:

Java
xxxxxxxxxx
1
17
 
1
package com.tn.jlink.example;
2
3
/**
4
 * Hello world!
5
 *
6
 
7
import java.util.logging.Logger;
8
9
public class HelloWorld
10
 {
11
    private static final Logger LOG = Logger.getLogger(HelloWorld.class.getName());
12
    
13
    public static void main( String[] args )
14
    {
15
        LOG.info( "Hello World!" );
16
    }
17
}


Now, modify the pom by adding this section after the <dependencies>.

XML
xxxxxxxxxx
1
12
 
1
<build>
2
    <plugins>
3
     <plugin>
4
    <groupId>org.apache.maven.plugins</groupId>
5
    <artifactId>maven-compiler-plugin</artifactId>
6
    <version>3.8.0</version>
7
    <configuration>
8
        <release>11</release>
9
    </configuration>
10
     </plugin>  
11
    </plugins>
12
</build>


Note that for an application that is Java 9 and beyond, the compiler plugin has to be 3.8 or higher.

You can check this by building it (Run -> Run as…, Maven Build). For Goals enter clean install, and under target directory, you will find the example-0.0.1-SNAPSHOT.jar file.

That concludes the introduction on how to build a modular Java project as a modular JAR. We now turn to compiling this with jLink. The command line syntax to run jLink is:

Shell
xxxxxxxxxx
1
 
1
jlink [options] –module-path modulepath
2
  –add-modules module [, module…]
3
  --output <target-directory>


But, it must have been years since anyone used the command line to build a Java project. Besides, the command line in Windows sucks. Instead we will the use the ModiTect plugin for Maven . It supports many goals for the Java Module system, among them the one we are interested it, namely creating a custom runtime image. Now add the following to the pom

XML
xxxxxxxxxx
1
36
 
1
<plugin>
2
        <groupId>org.moditect</groupId>
3
        <artifactId>moditect-maven-plugin</artifactId>
4
        <version>1.0.0.Beta2</version>
5
        <executions>
6
            <execution>
7
                <id>create-runtime-image</id>
8
                <phase>package</phase>    
9
                <goals>
10
                    <goal>create-runtime-image</goal>
11
                </goals>
12
            <configuration>
13
                <modulePath>            
14
                <path>               
15
    ${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging} 
16
                </path>
17
                </modulePath>
18
            <modules>
19
                <module>com.tn.jlink</module>   
20
         </modules>
21
        <launcher>
22
          <name>hello</name>                  
23
          <module>
24
            com.tn.jlink/com.tn.jlink.example.HelloWorld
25
          </module>                           
26
        </launcher>
27
        <outputDirectory>
28
            ${project.build.directory}/jlink-image
29
        </outputDirectory>                    
30
        <stripdebug>yes</stripdebug>
31
        <noManPages>yes</noManPages>
32
        <noHeaderFiles>yes</noHeaderFiles>
33
      </configuration>
34
    </execution>
35
  </executions>
36
</plugin>  


A brief explanation of the elements:

  • modulePath: is the path to the directories that contain the modules. For us, we just put the JAR file we just built on this path. Note that the java.logging module is a Java module that is implicitly included. We cheated a little bit by using a Java module. Most modules are in external JAR files, and to keep this element simple, we should put the JARs in a sub-directory and put that on the module path.
  • modules/module: we just include the name we entered when we created the module-info. We list out the modules, by given name, one per module element.
  • outputDirectory: directory in which the runtime image should be created
  • launcher: jLink can create shell scripts to launch the main class. Here, we give the file name. We called it "hello".
  • launcher/module: this is a bit confusing. If you don’t get it right, you will get an error that you didn’t specify the main class. This is the fully qualified name of the class we want to launch, which is moduleName/fully qualified class name.
  • stripDebug: whether to strip debug symbols or not. This is a jLink command line option. We set it to true to reduce the size of the image
  • noManPages, noHeaderFiles: same as above.

Now, if we build it, you will find a new subdirectory of target called, as we asked, jlink-image. Look in the bin directory. In it are the launch scripts ‘hello’ and ‘hello.bat’. Run it to see:

Plain Text
xxxxxxxxxx
1
 
1
INFO: Hello World!


Now we come to the final step of stuffing it into a Docker image. As Java developers, we are used to being platform agnostic. But the jLink image is dependent on the platform on which it was built.  Recall that Docker runs a small in-memory Linux kernel. If we built this on say, Windows it will not work. So, we need to build the image on linux. Since we wish to keep the image small, as a base image, we will use the alpine image which is just under 5MB.

Now, we run into a different problem. Alpine distributions use musl instead of libgc used by other Linux distributions. They are both C++ APIs over the Linux kernel. C or C++ code which is compiled against glibc will not run on a musl system, and vice-versa. And the JVM is written in C++ (at least for now). Bottom line is we must build it on Alpine. If you look at Maven distros in Docker Hub, there is only a Java 12 alpine distro, which is what we will use. Finally, here is the Dockerfile (which we will create under the project in Eclipse)

Dockerfile
xxxxxxxxxx
1
11
 
1
FROM maven:3.6-jdk-12-alpine as build
2
3
WORKDIR /wrk
4
COPY pom.xml .
5
COPY src src
6
RUN mvn clean install
7
8
FROM alpine:3.8
9
10
COPY --from=build /wrk/target/jlink-image /app
11
ENTRYPOINT ["/app/bin/hello"]


Now, build the image and call it “example”.

Shell
xxxxxxxxxx
1
 
1
docker image build -t example


And run it

Shell
xxxxxxxxxx
1
 
1
docker container run -ti example


And see the INFO:Hello World. Run the docker images command to see the size:

Plain Text
xxxxxxxxxx
1
 
1
REPOSITORY  TAG       IMAGE ID                CREATED           SIZE
2
example     latest     89e421476953        47 hours ago        54MB


Usually, the size of a Java Hello World image is north of a 120 MB. Using jLink yields a significantly smaller image.

We end with a word of caution. Image size is not everything. An image is downloaded once and cached and used over many, many applications. But, a jLink image is specific to the application and is not reusable. So, your mileage will vary depending on what you are trying to do.

JRE Java (programming language) application Alpine Linux Plain text Docker (software) Build (game engine) Eclipse Command (computing)

Opinions expressed by DZone contributors are their own.

Trending

  • Building and Deploying Microservices With Spring Boot and Docker
  • JavaFX Goes Mobile
  • Reducing Network Latency and Improving Read Performance With CockroachDB and PolyScale.ai
  • Google Becomes A Java Developer's Best Friend: Instantiations Developer Tools Relaunched For Free

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

Let's be friends: