In a recent article, I created an isolated Docker Container as Java 9 R&D environment: https://technology.amis.nl/2017/10/11/quick-and-clean-start-with-java-9-running-docker-container-in-virtualbox-vm-on-windows-10-courtesy-of-vagrant/. In this article, I will use that environment to take few small steps with Java 9 – in particular with modules. Note:this story does not end well. I wanted to conclude with using jlink to create a stand alone runtime that contained both the required JDK modules and my own module – and demonstrate how small that runtime was. Unfortunately, the Link step failed for me. More news on that in a later article.
Create Custom Module
Start a container based on the openjdk:9 image, exposing its port 80 on the docker host machine and mapping folder /vagrant (mapped from my Windows host to the Docker Host VirtualBox Ubuntu image) to /var/www inside the container:
docker run -it -p 127.0.0.1:8080:80 -v /vagrant:/var/www openjdk:9 /bin/sh
Create Java application with custom module: I create a single Module (nl.amis.j9demo) and a single class nl.amis.j9demo.MyDemo. The module depends directly on one JDK module (httpserver) and indirectly on several more.
The root directory for the module has the same fully qualified name as the module: nl.amis.j9demo.
This directory contains the module-info.java file. This file specifies:
- which modules this module depends on
- which packages it exports (for other modules to create dependencies on)
In my example, the file is very simple – only specifying a dependency on jdk.httpserver:
The Java Class MyDemo has a number of imports. Many are for base classes from the java.base module. Note: every Java module has a implicit dependency on java.base, so we do not need to include it in the modue-info.java file.
This code create an instance of HttpServer – an object that listens for HTTP Requests at the specified port (80 in this case) and then always returns the same response (the string “This is the response”). As meaningless as that is – the notion of receiving and replying to HTTP Requests in just few lines of Java Code (running in the OpenJDK!) is quite powerful.
package nl.amis.j9demo; import java.io.*; import java.net.*; import java.util.*; import java.util.concurrent.*; import java.util.stream.*; import com.sun.net.httpserver.*; import static java.lang.System.out; import static java.net.HttpURLConnection.*; public class MyDemo{ private static final int DEFAULT_PORT = 80; private static URI ROOT_PATH = URI.create("/"); private static class MyHandler implements HttpHandler { public void handle(HttpExchange t) throws IOException { URI tu = t.getRequestURI(); InputStream is = t.getRequestBody(); // .. read the request body String response = "This is the response"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } public static void main(String[] args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(DEFAULT_PORT), 0); server.createContext("/apps ", new MyHandler()); server.setExecutor(null); // creates a default executor server.start(); out.println("HttpServer is started, listening at port "+DEFAULT_PORT); } }
Compile, Build and Run
Compile the custom module:
javac -d mods –module-source-path src -m nl.amis.j9demo
Create destination directory for JAR file
mkdir -p lib
Create the JAR for the module:
jar –create –file lib/nl-amis-j9demo.jar –main-class nl.amis.j9demo.MyDemo -C mods/nl.amis.j9demo .
Inspect the JAR file:
jar tvf lib/nl-amis-j9demo.jar
To run the Java application- with a reference to the module:
java –p lib/ -m nl.amis.j9demo
the traditional equivalent with a classpath for the JAR file(s) would be:
java -classpath lib/nl-amis-j9demo.jar nl.amis.j9demo.MyDemo
Because port 80 in the container was exposed and mapped to port 8080 on the Docker Host, we can access the Java application from the Docker Host, using wget:
wget 127.0.0.1:8080/apps
The response from the Java application is hardly meaningful However, the fact that we get a response at all is quite something: the ‘remote’ container based on openjdk:9 has published an HTTP server from our custom module that we can access from the Docker Host with a simple HTTP request.
Jlink
I tried to use jlink – to create a special runtime for my demo app, consisting of required parts of JDK and my own module. I expect this runtime to be really small.
The JVM modules by the way on my Docker Container are in /docker-java-home/jmods
The command for this:
jlink –output mydemo-runtime –module-path lib:/docker-java-home/jmods –limit-modules nl.amis.j9demo –add-modules nl.amis.j9demo –launcher demorun=nl.amis.j9demo –compress=2 –no-header-files –strip-debug
Unfortunately, on my OpenJDK:9 Docker Image, linking failed with this error:
Error: java.io.UncheckedIOException: java.nio.file.FileSystemException: mydemo-runtime/legal/jdk.httpserver/ASSEMBLY_EXCEPTION: Protocol error
Resources
Documentation for jlink – https://docs.oracle.com/javase/9/tools/jlink.htm
JavaDoc for HttpServer package – https://docs.oracle.com/javase/9/docs/api/com/sun/net/httpserver/package-summary.html#
Java9 Modularity Part 1 (article on Medium by Chandrakala) – https://medium.com/@chandra25ms/java9-modularity-part1-a102d85e9676
JavaOne 2017 Keynote – Mark Reynolds demoing jlink – https://youtu.be/UNg9lmk60sg?t=1h35m43s
Exploring Java 9 Modularity – https://www.polidea.com/blog/Exploring-Java-9-Java-Platform-Module-System/