Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

First steps with Java 9 and Project Jigsaw – Part 2

1.12.2015 | 12 minutes of reading time

This is part 2 of a series that aims to get you started with project Jigsaw. In part 1 , we briefly talked about the definition of a module and how the Java Runtime was modularized. We then proceeded to a simple example that demonstrated how to (and not to) compile, package and run a modular application.

In this post, we try to answer the following questions:

  • Can I put a restriction on which modules can read an exported package?
  • What about having different versions of a module on the modulepath?
  • How does Jigsaw interoperate with non-modular legacy code?
  • How do I build my own Java runtime image?

We’ll take the example from part 1 as a base and continue to play with it. The code remains available here .

Restricting readability to specific modules

In part one , we talked about how Jigsaw evolves Java accessiblity. One accessibility level that was mentioned but not elaborated on was “public to some modules that read this module”. In this case, we are able to restrict which modules are allowed to read our exported packages. So if the developers of de.codecentric.zipvalidator really dislike the team behind de.codecentric.nastymodule, they can change their module-info.java to this:


module de.codecentric.zipvalidator{

    exports de.codecentric.zipvalidator.api 
        to de.codecentric.addresschecker;
}

This allows only the addresschecker to access the zipvalidator API. The specification happens on a package level, so you are perfectly able to restrict access of some package but allow full access for others. This is known as qualified exports. If de.codecentric.nastymodule tries to access any type from de.codecentric.zipvalidator.api, there is a compilation error:

1./de.cc.nastymodule/de/cc/nastymodule/internal/AddressCheckerImpl.java:4: 
2error: ZipCodeValidatorFactory is not visible 
3       because package de.cc.zipvalidator.api is not visible

Note that there is no complaint about module-info.java as the zipvalidator could actually export visible packages to the nastymodule. Qualified exports can for example be used when you want to modularize your application internally, but do not want to share the internal modules’ export packages with clients.

Conflicting module versions

A common scenario is ending up having different versions of a library in the same application via transitive dependencies, so we could find ourselves in a situation in which a module is on the modulepath twice. Two scenarios come to mind:

  • The modules are available at compile time in different folders or modular jars, but still share the same module name
  • The different versions of a module have different module names

Let’s try to compile the application in the first scenario. The zipvalidator has been copied:


two-modules-multiple-versions
├── de.codecentric.addresschecker
│   ├── de
│   │   └── codecentric
│   │       └── addresschecker
│   │           ├── api
│   │           │   ├── AddressChecker.java
│   │           │   └── Run.java
│   │           └── internal
│   │               └── AddressCheckerImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator.v1
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           ├── internal
│   │           │   └── ZipCodeValidatorImpl.java
│   │           └── model
│   └── module-info.java
├── de.codecentric.zipvalidator.v2
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           ├── internal
│   │           │   └── ZipCodeValidatorImpl.java
│   │           └── model
│   └── module-info.java

The duplicated modules reside in different folders, but the module name remains unchanged. What has Jigsaw to say about that during compilation?

1./de.codecentric.zipvalidator.v2/module-info.java:1: 
2error: duplicate module: de.codecentric.zipvalidator

Ok, so we’re not getting away with this. Jigsaw produces compile error when there are two modules with the same name on the modulepath.

What about case two? The directory structure remains the same, but now both zipvalidators get different names (de.codecentric.zipvalidator.v{1|2}) and the addresschecker reads both of them


module de.codecentric.addresschecker{
     exports de.codecentric.addresschecker.api;
     requires de.codecentric.zipvalidator.v1;
     requires de.codecentric.zipvalidator.v2;
}

Surely, this cannot compile either? Reading two modules that export the same packages? Actually, it does. I was surprised by this – the compiler acknowledges the situation, but settles for warnings like this one:

1./de.cc.zipvalidator.v1/de/codecentric/zipvalidator/api/ZipCodeValidator.java:1: 
2warning: package exists in another module: de.codecentric.zipvalidator.v2

Being developers, a warning is readily ignored and the application is run. Jigsaw really doesn’t like what it sees at runtime, though:

1java.lang.module.ResolutionException: 
2Modules de.codecentric.zipvalidator.v2 and de.codecentric.zipvalidator.v1 export 
3package de.codecentric.zipvalidator.api to module de.codecentric.addresschecker

I personally think this is not intuitive and a compile time error might be nicer. I asked on the mailing list about the motivation behind this choice, but have yet to receive a response at the time of writing.

Automatic modules and the unnamed module

So far, we have been working in an all-module environment. But what about those very likely cases where we have to work with non-modular Jar files? This is where automatic modules and the unnamed module come into play.

Let’s start with automatic modules. An automatic module is a jar that you put on the modulepath. Once you put it there, it answers the three questions like this

Q: What’s it’s name?
A: It is the name of the jar. So if you put guava.jar on the modulepath, you get an automatic module called guava. This also means that you cannot use a Jar straight from a Maven repository because guava-18.0 is not a valid Java identifier.

Q: What does it export?
A: An automatic module exports all its packages. So all public types are available to every module that reads an automatic module.

Q: What does it require?
A: An automatic module reads *all* other available modules (including the unnamed module, more on that later). This is important! You can access all exported types of any other module from an automatic module. You do not have to specifiy this anywhere, it is implied.

Let’s try an example. We start to use com.google.common.base.Strings in the zipvalidator. To allow this access, we must define a read edge to Guava’s automatic module:


module de.codecentric.zipvalidator{
       exports de.codecentric.zipvalidator.api;
       requires public de.codecentric.zipvalidator.model;
       requires guava;

 }

For compilation, we need to put the guava.jar on the modulepath (it’s in folder ../jars):

1javac -d . -modulepath ../jars -modulesourcepath .  $(find . -name "*.java")

This compiles and runs just fine.

(For the record, this example was not easy to get working. I got into some issues with this using Jigsaw build 86 because it complained about dependencies to a module called jdk.management.resource. I asked on the mailing list about this, you can see the conversation here .
Basically, the solution was not using the early access build but building the JDK myself. There were some more issues with this on OSX Mavericks as you can see in the thread, I had to change a makefile, but in the end I got it working. Your mileage may vary with later releases).

This is a good time to introduce you to your new best friend during the transition to Jigsaw. There is a tool called jdeps that takes a look at your non-modular code and tells you about its dependencies. Let’s look at guava:

1jdeps -s ../jars/guava.jar

has the following output

1guava.jar -> java.base
2guava.jar -> java.logging
3guava.jar -> not found

This means that the automatic guava module requires java.base, java.logging and … “not found?! What is that? Leaving out the -s switch, jdeps leaves the module perspective and goes down a step to package level (shortened for brevity, guava has quite a few packages):

1com.google.common.xml (guava.jar)
2      -> com.google.common.escape                           guava.jar
3      -> java.lang
4      -> javax.annotation                                   not found

Here, we see that the com.google.common.xml package depends on com.google.common.escape which it located in the module itself, java.lang which is well-known and javax.annotation which is not found. This tells us that we need a jar containing JSR-305 types as this contains javax.annotation (I actually do not do this for these examples – i don’t need any type from this package in my examples and neither the compiler nor the runtime cares).

The unnamed module

So what is the unnamed module? Let’s answer the three questions again:

Q: What’s it’s name?
A: If you haven’t guessed yet, the unnamed module doesn’t have a name

Q: What does it export?
A: The unnamed module exports all its packages to any other module. That does not mean you can read it from any other module – it doesn’t have a name, so you cannot require it! requires unnamed; does not work.

Q: What does it require?
A: The unnamed module reads all other available modules.

So, if you cannot read the unnamed module from any of your modules, what’s the point? To answer this, we meet an old friend – the classpath. Every type read from the classpath (instead of the modulepath) is automatically placed in the unnamed module – or differently put, every type in the unnamed module has been loaded via classpath. Since the unnamed module reads every other module, we can access all exported types from any classpath-loaded type. Java 9 will support use of classpath and modulepath either in isolation or even mixed to ensure downward compatibility. Let’s look at some examples.

Let’s assume we still have our nice zipvalidator module, but our addresschecker is still non-modular and does not have a module-info.java. This is our source structure


one-module-with-unnamed-ok/
├── classpath
│   └── de.codecentric.legacy.addresschecker
│       └── de
│           └── codecentric
│               └── legacy
│                   └── addresschecker
│                       ├── api
│                       │   ├── AddressChecker.java
│                       │   └── Run.java
│                       └── internal
│                           └── AddressCheckerImpl.java
├── modulepath
│   └── de.codecentric.zipvalidator
│       ├── de
│       │   └── codecentric
│       │       └── zipvalidator
│       │           ├── api
│       │           │   ├── ZipCodeValidator.java
│       │           │   └── ZipCodeValidatorFactory.java
│       │           └── internal
│       │               └── ZipCodeValidatorImpl.java
│       └── module-info.java

There is now one folder called classpath that contains the legacy code that wants to access the zipvalidator, and a folder called modulepath that contains the zipvalidator module. We can compile our modules in the usual way. To compile the legacy code, we need to provide info of the modular code. We do this by putting it on the classpath:

1javac -d classpath/de.codecentric.legacy.addresschecker  
2  -classpath modulepath/de.codecentric.zipvalidator/ $(find classpath -name "*.java")

This just works as usual.

At runtime, we have two options now. We can

  • put the module on the classpath
  • mix classpath and modulepath

Using the first option effectively means that we do not use the module system. All types are put in the unnamed module where they can freely access each other.

1java -cp modulepath/de.cc.zipvalidator/:classpath/de.cc.legacy.addresschecker/
2    de.codecentric.legacy.addresschecker.api.Run 76185

behaves exactly like the java application you’re using today.

Mixing classpath and module path on the other hand works like this

1java -modulepath modulepath  -addmods de.codecentric.zipvalidator 
2    -classpath classpath/de.codecentric.legacy.addresschecker/ 
3    de.codecentric.legacy.addresschecker.api.Run

We use both the -classpath and -modulepath switches. A new addition is the -addmods switch – when mixing classpath and modulepath, we don’t just get access to any module in the modulepath folders, we have to specifically state which ones should be available.

This approach works fine too, but there is a caveat! Remember, the answer to “what does the unnamed module require” is “all other modules”. If we use the zipvalidator module via the modulepath, we can only use its exported packages. Anything else will result in an IllegalAccessError at runtime. So you have to stick to the rules of the module system in this case.

Creating runtime images with jlink

That’s it for the module examples, but there is another new tool that deserves our attention. jlink is Java 9’s utility to create your own JVM distributions. The cool thing is that due to the newly modularized nature of the JDK, you can pick which modules you want to include in this distribution! Let’s see an example. If we want to create a runtime image that includes our addresschecker, we issue the command

1jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/ 
2    --addmods de.codecentric.addresschecker --output linkedjdk

We only specify three things:

  • the modulepath (including your custom modules and the path to the jmods folder in your JDK – this includes the standard java modules)
  • the modules you want to include in your distribution
  • an output folder

This command creates the following:


linkedjdk/
├── bin
│   ├── java
│   └── keytool
├── conf
│   ├── net.properties
│   └── security
│       ├── java.policy
│       └── java.security
└── lib
    ├── classlist
    ├── jli
    │   └── libjli.dylib
    ├── jspawnhelper
    ├── jvm.cfg
    ├── libjava.dylib
    ├── libjimage.dylib
    ├── libjsig.diz
    ├── libjsig.dylib
    ├── libnet.dylib
    ├── libnio.dylib
    ├── libosxsecurity.dylib
    ├── libverify.dylib
    ├── libzip.dylib
    ├── modules
    │   └── bootmodules.jimage
    ├── security
    │   ├── US_export_policy.jar
    │   ├── blacklisted.certs
    │   ├── cacerts
    │   └── local_policy.jar
    ├── server
    │   ├── Xusage.txt
    │   ├── libjsig.diz
    │   ├── libjsig.dylib
    │   ├── libjvm.diz
    │   └── libjvm.dylib
    └── tzdb.dat

That’s it. On OSX Mavericks, this is about 47 MB in size. We can also enable compression and remove some debugging features that you wouldn’t need on a production system anyway. The smallest distribution I have managed so far was using the following command:

1jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/bin 
2    --addmods de.codecentric.addresschecker --output linkedjdk --exclude-files *.diz 
3    --compress-resources on --strip-java-debug on --compress-resources-level 2

This reduces the distribution to about 18 MB, which sounds pretty cool to me. Apparently you can get it down to 13 on Linux

Calling

1/bin/java --listmods

shows the modules that are contained in this distribution

1de.codecentric.addresschecker
2de.codecentric.zipvalidator
3java.base@9.0

So all applications that depend on a maximum of those modules can run on this JVM. I have not been able to get our main class to run in this scenario though. To do this, I used another way:

A keen observer might have noticed that the second call to jlink had a different modulepath than the first one. In the second one, we specify a path to a folder named bin. This folder contains modular jars, and jar for the addresschecker also contains a main-class info in its Manifest. jlink uses this information to add an extra entry to our JVM bin folder:


linkedjdk/
├── bin
│   ├── de.codecentric.addresschecker
│   ├── java
│   └── keytool
...

This means that now, we can call our application directly. Sweet!

1./linkedjdk/bin/de.codecentric.addresschecker 76185

prints

176185 is a valid zip code

Conclusion

This concludes our introduction to Jigsaw. We went through some examples showing what you can and cannot do with Jigsaw and Java 9. Jigsaw will be a somewhat disruptive change that cannot be simply picked up like Lambdas or try-with-resources. Our whole tool chain from build tools like Maven or Gradle to IDEs will need to adapt to the module system. At JavaOne, Hans Dockter of Gradle Inc. held a session showing how you can already start writing modular code even with Java <9 -="" Gradle="" will="" perform="" checks="" at="" compile="" time="" and="" fail="" if="" module="" integrity="" is="" violated.="" This="" (experimental)="" feature="" has="" been="" included="" in="" the="" recent="" release="" of="" Gradle 2.9. Interesting times are certainly ahead!

For more information about Jigsaw I once more recommend the Jigsaw Project home page , especially the slides and videos from this year’s JavaOne sessions on Jigsaw and project lead Mark Reinhold’s http://openjdk.java.net/projects/jigsaw/spec/sotms/.

share post

Likes

0

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.