For many years, Java has been the number one in the TIOBE index of the most commonly used programming languages.
Java is an object-oriented language that was developed with great attention to portability. Today, Java code runs virtually everywhere: on mainframes as well as on smartphones. Java is well suited for programming novices but lacks a more elegant notation in many areas. In that context, modern programming languages such as Ruby or current JavaScript versions are more convenient to use.
Java, as a compiled language with huge libraries, is actually not predestined for Docker use. But since Oracle had the glorious idea to drive developers and Linux distributors crazy with semiannual major releases, Docker is a good way to run or further develop individual projects independently in suitable Java releases.
The official Java images on Docker Hub include the OpenJDK implementation of the Java Developer Kit (https://hub.docker.com/_/eclipse-temurin). In July 2021, the major versions 8, 11, 16, 17, and 18 were available for selection there, with 16 marked as the latest as it was considered to be the most stable version at that time. The number of different build variants for the five versions is so large that it’s no longer even displayed on the Docker Hub website. Among them are builds based on different versions of WindowsServerCore and various Linux distributions (Debian and Oracle Linux).
The decision as to whether you want the entire development environment or just the runtime has at least been taken away from you by the developers in the more recent versions: current Docker images of OpenJDK are only available complete with the compiler. The space requirements of the OpenJDK images are considerable.
Unfortunately, we couldn’t find an easy-to-install and up-to-date RSS library for Java. For this reason, we’ll extract the news directly from the Slashdot news ticker website in the following example. We’ll do this by using an add-on library, jsoup, which allows you to search the Document Object Model (DOM) tree of an HTML document using tags and Cascading Style Sheet (CSS) classes. The drawback of this approach is that this program stops working as soon as the website owner changes the design, that is, renames the CSS classes. Here you can see the Java class for extracting the message list:
// File: prog/java/printheadlines.java
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
public class printheadlines {
private static String url = "https://slashdot.org/";
public static void main(String[] args) throws IOException {
Document doc = Jsoup.connect(url).get();
Elements news = doc.select("article h2.story>span.story-title>a");
for (Element item : news) {
System.out.println("* "+ item.text());
}
}
}
In the main function of our small Java program, we first load the HTML page with the jsoup function connect(url).get(). The following doc.select call creates a list of HTML elements that match the CSS statement. In the for loop, the elements are output to the screen, with the text() function of jsoup removing spaces and other HTML tags within the element.
In Java, it’s common for external libraries to be downloaded as jar files and specified in the classpath both at compile time and at execution time. In our Dockerfile, we’ll download the library in the currently current version 1.11.3 from the project’s website and store it in the Docker image. In addition, when creating the Docker image, we’ll translate the source code so that a derived container has access to the final compiled program.
# File: prog/java/Dockerfile (docbuc/printheadlines:java)
FROM openjdk:16
RUN adduser -r java
WORKDIR /src
RUN chown java /src
USER java
ENV JSOUP_VER 1.13.1
RUN curl -SL https://jsoup.org/packages/jsoup-$JSOUP_VER.jar \
-o jsoup-$JSOUP_VER.jar
COPY printheadlines.java /src/
RUN javac -verbose -cp /src/jsoup-$JSOUP_VER.jar:. \
printheadlines.java
CMD java -cp jsoup-$JSOUP_VER.jar:. printheadlines
For security reasons, we don’t want to run the container as root user. Because there’s no suitable unprivileged user in the Oracle Linux system, we’ll create the user via the adduser command, where the -r parameter specifies that the user becomes a system user and thus won’t get a normal login.
Specifying an environment variable, in this case, JSOUP_VER, is handy if you want to update the library at a later time. As long as the structure of the download links remains the same, it’s then sufficient to adjust the version number.
For the first attempt, we use version 16 of the Java Developer Kit (JDK), which as of July 2021 was marked as latest. This has probably changed since this post was written: Oracle has significantly accelerated the development cycle and releases new major versions once every six months.
No matter how this evolution continues, Docker gives you the ideal tool to test and deliver your software with different Java versions. You just need to change the version number of the official Docker image in your Dockerfile from 16 to 17, and you’ll be testing with the latest developer version. Parallel installations of different versions of the JDK in one operating system are basically possible, but, in practice, they always cause difficulties. Docker is made to save you trouble in the process.
What you should do here is build the Docker image using docker build -t docbuc/printheadlines: java ., and then run it via docker run docbuc/printheadlines:java. Here you can see the slightly shortened output of the build process:
Sending build context to Docker daemon 3.584kB
Step 1/10 : FROM openjdk:16
---> f4f1dadedfab
Step 2/10 : RUN adduser -r java
---> Running in ac0ca27aee3c
Removing intermediate container ac0ca27aee3c
---> 0cef2ee2d7f2
[...]
Step 9/10 : RUN javac -cp /src/jsoup-$JSOUP_VER.jar:. printh...
---> Running in 232ab7613846
Removing intermediate container 232ab7613846
---> d6bc5ff51cba
Step 10/10 : CMD java -cp jsoup-$JSOUP_VER.jar:. printheadlines
---> Running in ee090202f84b
Removing intermediate container ee090202f84b
---> 5cafad4b0f90
Successfully tagged docbuc/printheadlines:java
Try changing the Java version in your Dockerfile. Our tests with version 18 were successful.
Older Java versions were known to be too wasteful with RAM and CPU power. This blog post describes the problem and some suggested solutions.
If you use Linux as a Docker host, the Java Virtual Machine (JVM) of OpenJDK 8 and 9 can detect the limits provided in Docker for the container when you invoke the Java interpreter with the following options:
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
Starting with OpenJDK 11, you can save yourself the trouble. As of this version, Java itself recognizes the restrictions that apply to containers on Linux.
The situation is different if you use Windows as a Docker host. In that case, you can use the start command in Windows to control which resources java is allowed to use via options:
Editor’s note: This post has been adapted from a section of the book Docker: Practical Guide for Developers and DevOps Teams by Bernd Öggl and Michael Kofler