Demoing a simple module in Java 9

In the spirit of the upcoming Java 9 release, I decided to port my D version of the “Guessing game” into Java, but with a twist. I thought that it would be a nice touch to develop the game into a nice module, and turn the process of developing it into an approachable starter tutorial for creating, using, and most importantly, understanding the basics of Java 9 modules.

Background

One of the biggest changes in Java 9 is the introduction of support for modules into the language itself. The rationale and reasoning behind it is very well documented on the OpenJDK site under JEP261.

However, if you find that heavy reading, a wonderfully succinct and clear Quick Start
is also provided for developers who want to simply get on with it. This post is based on that tutorial.

Before we get started with the actual game, I just wanted to ruminate a bit on why I think it’s a welcome idea that modules are being introduced into the language itself. A lot of people conflate packages in Java with modularity. Well, in a sense that does make sense. However, I like to view it as more of a namespacing mechanism than anything else.

Modularity, by definition, would imply a stronger separation of concerns – classes and interfaces wrapped in packages wrapped within modules. Java 9 modules achieve precisely that – they control which packages (and thereby which classes) are visible outside the module itself. Whereas previously we could just toss any old JAR file into the classpath and load classes at will, now we can only do so if the module they reside in explicitly expose them. Very nice!

The game

The game is dead simple – the program generates a secret number in the range [1, 100], and it’s the player’s job to guess the number.

The setup

Okay, so let’s start off by creating a directory for our game module.

Macushla:modules z0ltan$ mkdir guessing-game
Macushla:modules z0ltan$ cd guessing-game

Now, let’s create a directory called mods where we will store the generated module, and also a directory called src (following convention, not strictly enforced) which will hold the module source code:

Macushla:guessing-game z0ltan$ mkdir mods
Macushla:guessing-game z0ltan$ mkdir src
Macushla:guessing-game z0ltan$ tree .
.
├── mods
└── src

2 directories, 0 files

The naming convention for modules is identical to that of packages (com.foo.bar), so pay close attention to the next few steps. Inside the src directory, let us create a directory named com.guessing (note: it is a single directory, not nested
directories). Also, inside the com.guessing directory, let us create a file called module-info.java:

Macushla:guessing-game z0ltan$ mkdir src/com.guessing
Macushla:guessing-game z0ltan$ touch src/com.guessing/module-info.java
Macushla:guessing-game z0ltan$ tree .
.
├── mods
└── src
    └── com.guessing
        └── module-info.java

3 directories, 1 file

So here’s how it works – the directory com.guessing represents the module that we are creating for the game, and it is signified by the module declaration that we will store inside module-info.java. The casing and hyphenation might throw you off a bit, but that’s how Java knows that you have declared a module, so make sure to get the name right!

Right, so let’s go on ahead and write up the module declaration inside module-info.java. Open up the file in your favourite text editor, and then enter the following metadata inside it:

 module com.guessing {
    exports com.my.guessing.game;
 }

Let’s take a moment to understand this bit – the module keyword is how you introduce a module declaration. So we’re declaring here that com.guessing is a module, and that we are exposing the package structure, com.my.guessing.game
from this module. What does this mean? Well, it means that any other module that wishes to use our module can only access the classes that are in this package structure. This is how modules implement real modularity where only the code that needs to be visible to the outside world is made so.

Since we have declared that we are exposing the package com.my.guessing.game, it follows that we should have that package structure in our src directory as well. Let’s go ahead and
create it:

Macushla:guessing-game z0ltan$ mkdir -p src/com.guessing/com/my/guessing/game

And let’s create out single source file named TheGame.java inside the same location:

Macushla:guessing-game z0ltan$ touch src/com.guessing/com/my/guessing/game/TheGame.java

So here’s how the project tree looks at this stage:

Macushla:guessing-game z0ltan$ tree .
.
├── mods
└── src
    └── com.guessing
        ├── com
        │   └── my
        │       └── guessing
        │           └── game
        │               └── TheGame.java
        └── module-info.java
        

Excellent! So, let’s fill out the code for the game now, inside TheGame.java

package com.my.guessing.game;

import java.util.Random;
import java.util.Scanner;

public class TheGame {
	private static Random rand;

	public TheGame() {
		rand = new Random();
	}

	public static void main(String[] args) {
		TheGame game = new TheGame();
	
		game.play();
	}

	private void play() {
		try{
			Scanner in = new Scanner(System.in);		

			System.out.println("Welcome to the Guessing Game!");

			// secret number in the range [1, 100]
			int mySecretNumber = rand.nextInt(100) + 1;
			int attempts = 0;

			while (true) {
				attempts++;

				System.out.println("Enter your guess (between 1 and 100 inclusive)");
				int guess = in.nextInt();

				if (guess < mySecretNumber) {
					System.out.println("Too small!");
				} else if (guess == mySecretNumber) {
					System.out.format("You win! You took %d guesses!\n", attempts);
				} else {
					System.out.println("Too big!");
				}
			}
		} catch (Exception ex) {
			System.err.println("Oh no! Something went terribly wrong. Aborting game!");
			System.exit(1);
		}
	}
}

Nice! So now all we have left to do is to compile the game and run it. Ah, but there remains one more step before we can actually do that. Remember the mods directory we had created in the first step? That directory is meant to hold all the modules that we would generate for our project (there can be as many modules as you like in your project, with various modules dependent on various other modules). In our game, we have only one module,
com.guessing. To hold the contents of this module, we need to create the corresponding module directory in the mods directory, like so:

Macushla:guessing-game z0ltan$ mkdir mods/com.guessing
Macushla:guessing-game z0ltan$ tree .
.
├── mods
│   └── com.guessing
└── src
    └── com.guessing
        ├── com
        │   └── my
        │       └── guessing
        │           └── game
        │               └── TheGame.java
        └── module-info.java

8 directories, 2 files

Excellent! Let’s go ahead and compile the game:

Macushla:guessing-game z0ltan$ javac --module-path=mods -d mods/com.guessing \
  src/com.guessing/module-info.java src/com.guessing/com/my/guessing/game/TheGame.java 
Macushla:guessing-game z0ltan$ tree .
.
├── mods
│   └── com.guessing
│       ├── com
│       │   └── my
│       │       └── guessing
│       │           └── game
│       │               └── TheGame.class
│       └── module-info.class
└── src
    └── com.guessing
        ├── com
        │   └── my
        │       └── guessing
        │           └── game
        │               └── TheGame.java
        └── module-info.java

12 directories, 4 files

Okay, the compilation command requires a bit of explanation – the --module-path option is used to indicate the root directory (or directories) where the module (or modules) is to be stored. In our case, that is the mods directory. The -d flag indicates where the generated class files are to be stored. In our case, this is the mods/com.guessing
directory. Finally, we link in all the source files that need to be compiled, and this includes the module-info.java metadata file as well. Note that all this should be automatically done once IDE support is widely available for Java 9 (IntelliJ apparently has some support for Java 9 already,
and Eclipse also has an extra plugin that supports Java 9).

Also observe the contents of the mods directory. It’s an exact replica of the src directory albeit containing the generated classes.

As a final step, let’s run the game! This is how you would run it from the command-line:

Macushla:guessing-game z0ltan$ java --module-path mods -m com.guessing/com.my.guessing.game.TheGame
Welcome to the Guessing Game!
Enter your guess (between 1 and 100 inclusive)
50
Too small!
Enter your guess (between 1 and 100 inclusive)
75
Too big!
Enter your guess (between 1 and 100 inclusive)
63
Too big!
Enter your guess (between 1 and 100 inclusive)
56
You win! You took 4 guesses!

Again, a bit of commentary on the run command – we again use the --module-path option to tell Java where our module resides, and then we pass the actual fully-qualified name of the class to the -m flag, which tells Java which module we are interested in running. Note that there are various short forms (for instance, we can use
-p> instead of --module-path in this case, refer to the documentation for all the flags and options).

And we’re done!

Extras

One additional interesting feature in Java 9 is that we can bundle our game into a nice little app. To do that, we need to understand that Java 9 actually comes with all its functionality divided into modules. For instance, all core functionality resides in the java.base module. Also note that this module is automatically assumed to be used by all custom modules. For instance, the module declaration that we used for this game could very well have been written explicitly as:

module com.guessing {
   requires java.base;
   exports com.guessing.my.game;
}

where the requires module is the opposite of exports. Beware though that having the requires declaration is not enough to be able to use classes from that module. It only makes the exported packages in that module visible in the require-ing module. That means that in our code, we should still have explicit importS for those public packages. For instance, if we want to find out which modules are present
by default, we can view them so:

Macushla:guessing-game z0ltan$ java --list-modules
java.activation@9-ea
java.annotations.common@9-ea
java.base@9-ea
java.compact1@9-ea
java.compact2@9-ea
java.compact3@9-ea
java.compiler@9-ea
java.corba@9-ea
java.datatransfer@9-ea
java.desktop@9-ea
java.httpclient@9-ea
java.instrument@9-ea
java.jnlp@9-ea
java.logging@9-ea
java.management@9-ea
java.naming@9-ea
java.prefs@9-ea
java.rmi@9-ea
java.scripting@9-ea
java.se@9-ea
java.se.ee@9-ea
java.security.jgss@9-ea
java.security.sasl@9-ea
java.smartcardio@9-ea
java.sql@9-ea
java.sql.rowset@9-ea
... (content elided due to space contraints)

All the modules reside in the $JAVA_HOME/jmods directory, but you might be surprised if you check the contents – they won’t correspond to the output we saw above. The reason for this is the modules that come bundled with the JDK are in “jmod” form, which is a compiled format. The jmod tool comes bundled with the JDK, and can be used to create a jmod binary for our custom modules as well. The terminology for the module format that we have used here is “exploded module”. This JEP contains all the gruesomely interesting details.

Right, so where are we going with this? Ah yes, we wanted to bundle our game into a custom app. Let’s do that (but all this jmod discussion will come in handy later on)!

First off, let’s set our JAVA_HOME environment variable for convenience. Note, this section describes how to set it for macOS users only. For windows users, consult your documentation!

On macOS, one problem is that finding the paths of various installed tools can be a real PITA. Thankfully, we have a bundled tool called java_home to help us out:

Macushla:guessing-game z0ltan$ /usr/libexec/java_home -V
Matching Java Virtual Machines (3):
    9, x86_64:	"Java SE 9-ea"	/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home
    1.8.0_112, x86_64:	"Java SE 8"	/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home
    1.8.0_45, x86_64:	"Java SE 8"	/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home

/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home

Okay, so on my machine, I have three different versions of the JDK installed. Clearly, I need to use the Java 9 version, so let’s set the environment variable to use that:

Macushla:guessing-game z0ltan$ export JAVA_HOME="$(/usr/libexec/java_home -v 9)"
Macushla:guessing-game z0ltan$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home

Excellent. Now, let’s go ahead and create our app. For this, we use another new tool that comes bundled with the JDK – jlink. As the name suggests, it is a linker, and we use this linker to link our module into Java, and create a custom version of the JVM that contains only our dependencies (in addition to the mandatory java.base module):

Macushla:guessing-game z0ltan$ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules mods/com.guessing \
> --output my_guessing_app
Error: Module com.guessing not found

Module not found? How is it possible? We specified the fully-qualified module name, mods/com.guessing, didn’t we? Well, the reason jlink failed is that it accepts modules in only two formats – either JMOD (as discussed above), or as a JAR file containing our “exploded” module. So before we can bundle our app, there remains just one more step – bundling our app as a JAR or as JMOD. Let’s go for the jAR file
option:


Macushla:guessing-game z0ltan$ jar --create --file=guessing_game@0.0.1.jar \
  --module-version=0.0.1 --main-class=com.my.guessing.game.TheGame -C mods/com.guessing .

Note how we have to specify the module version explicitly. Java 9 modules do not (as yet) support automatic versioning, nor is versioning checked or enforced (as far as I can tell).

And now we can check that the JAR file contains a valid module by passing the --print-module-descriptor
flag to the jar command:

Macushla:guessing-game z0ltan$ jar --print-module-descriptor guessing_game\@0.0.1.jar 
^CMacushla:guessing-game z0ltan$ jar --print-module-descriptor --file guessing_game\@0.0.1.jar 

com.guessing@0.0.1
  requires mandated java.base
  exports com.my.guessing.game
  main-class com.my.guessing.game.TheGame

So it looks like all is in order. Let’s try creating the app now!

Macushla:guessing-game z0ltan$ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules mods/com.guessing/ \
> --output my_guessing_game
Error: Module mods/com.guessing/ not found
Macushla:guessing-game z0ltan$ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.guessing/ \
   --output my_guessing_game
Error: Module com.guessing/ not found

What gives? Note that the official convention is to create a directory to contain the JAR or JMOD file, called mlib. The modules that come bundled with the JDK all follow the same convention as well.

So let’s create a directory called mlib in our project root directory:

Macushla:guessing-game z0ltan$ jar --create --file=mlib/guessing_game@0.0.1.jar \
  --module-version=0.0.1 --main-class=com.my.guessing.game.TheGame \
   -C mods/com.guessing .

So the final directory structure looks like so:

Macushla:guessing-game z0ltan$ tree .
.
├── mlib
│   └── guessing_game@0.0.1.jar
├── mods
│   └── com.guessing
│       ├── com
│       │   └── my
│       │       └── guessing
│       │           └── game
│       │               └── TheGame.class
│       └── module-info.class
└── src
    └── com.guessing
        ├── com
        │   └── my
        │       └── guessing
        │           └── game
        │               └── TheGame.java
        └── module-info.java

13 directories, 5 files

The final step is to run the same command that we had tried before:

Macushla:guessing-game z0ltan$ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.guessing --output my_guessing_game
Macushla:guessing-game z0ltan$ ls
mlib             mods             my_guessing_game src

Excellent! So we have a directory called my_guessing_game created. Let’s check its
contents:

Macushla:guessing-game z0ltan$ tree my_guessing_game/
my_guessing_game/
├── bin
│   ├── com.guessing
│   ├── java
│   └── keytool
├── conf
│   ├── net.properties
│   └── security
│       ├── java.policy
│       ├── java.security
│       └── policy
│           ├── README.txt
│           ├── limited
│           │   ├── default_US_export.policy
│           │   ├── default_local.policy
│           │   └── exempt_local.policy
│           └── unlimited
│               ├── default_US_export.policy
│               └── default_local.policy
├── lib
│   ├── jli
│   │   └── libjli.dylib
│   ├── jspawnhelper
│   ├── jvm.cfg
│   ├── libjava.dylib
│   ├── libjimage.dylib
│   ├── libjsig.dylib
│   ├── libnet.dylib
│   ├── libnio.dylib
│   ├── libosxsecurity.dylib
│   ├── libverify.dylib
│   ├── libzip.dylib
│   ├── modules
│   ├── security
│   │   ├── blacklist
│   │   ├── blacklisted.certs
│   │   ├── cacerts
│   │   ├── default.policy
│   │   └── trusted.libraries
│   ├── server
│   │   ├── Xusage.txt
│   │   ├── libjsig.dylib
│   │   └── libjvm.dylib
│   └── tzdb.dat
└── release

10 directories, 34 files

Whoa! That’s a lot of files and directories. The good part is that it is now a full-fledged, self-contained Java app containing our game!

Let’s take it for a spin!

Macushla:guessing-game z0ltan$ cd my_guessing_game/
Macushla:my_guessing_game z0ltan$ ls
bin     conf    lib     release
Macushla:my_guessing_game z0ltan$ bin/
com.guessing  java          keytool       
Macushla:my_guessing_game z0ltan$ bin/com.guessing 
Welcome to the Guessing Game!
Enter your guess (between 1 and 100 inclusive)
50
Too big!
Enter your guess (between 1 and 100 inclusive)
25
Too small!
Enter your guess (between 1 and 100 inclusive)
38
Too small!
Enter your guess (between 1 and 100 inclusive)
44
Too small!
Enter your guess (between 1 and 100 inclusive)
47
Too big!
Enter your guess (between 1 and 100 inclusive)
46
Too big!
Enter your guess (between 1 and 100 inclusive)
45
You win! You took 7 guesses!

Noiiiice! However, before you get too excited, note that this is not AOT (Ahead of time compilation)! bin/com.guessing is simply a script file that uses the bundled JVM instance to run the app. We can verify this by checking the contents of com.guessing:

Macushla:bin z0ltan$ cat com.guessing 
#!/bin/sh
JLINK_VM_OPTIONS=
DIR=`dirname $0`
$DIR/java $JLINK_VM_OPTIONS -m com.guessing/com.my.guessing.game.TheGame $@

Still, pretty nice, innit? We have come a long way from your grandfather’s Java, my friends.

One last interesting observation before we collapse from mental overload! Recall the command we had used to check the modules bundled in the default JDK, java --list-modules? Well, let’s try out the same command on the bundled JVM instance in the bin
directory of our app:

Macushla:my_guessing_game z0ltan$ bin/java --list-modules
com.guessing@0.0.1
java.base@9-ea

Heh! As we expected, since our project depends only on two modules, com.guessing, which is our game, and on the mandatory module, java.base, we see that this stripped down JVM only has these two modules, which leads to a very small overall app size as well (if we had dependencies on other modules, then those would be transitively included
as well):

 Macushla:my_guessing_game z0ltan$ cd ..
Macushla:guessing-game z0ltan$ ls
mlib             mods             my_guessing_game src
Macushla:guessing-game z0ltan$ du -m my_guessing_game/
1	my_guessing_game//bin
1	my_guessing_game//conf/security/policy/limited
1	my_guessing_game//conf/security/policy/unlimited
1	my_guessing_game//conf/security/policy
1	my_guessing_game//conf/security
1	my_guessing_game//conf
1	my_guessing_game//lib/jli
1	my_guessing_game//lib/security
13	my_guessing_game//lib/server
31	my_guessing_game//lib
31	my_guessing_game/
Macushla:guessing-game z0ltan$

A mere 31MB with our bundled JVM! Ain’t that nice? Time for a nice beer now!

Advertisements
Demoing a simple module in Java 9