Eight years after its inception, Project Jigsaw – the modularization of the Java platform and introduction of a general module system – is on track to be included in Java 9. The target release has changed over the years from Java 7 via Java 8 to Java 9. The scope has changed a couple of times as well. Now, it really seems as if Jigsaw is getting ready for prime time as it was heavily featured in Oracle’s keynote for JavaOne 2015, with quite a few sessions focussing on the topic . What does this mean for you? What is Project Jigsaw and how can you use it?
This two-part blog series aims to give you a quick introduction to the module system and lots of practical code examples to demonstrate Jigsaw’s behaviour. In this first part, we’ll talk a bit about what the module system is, how the JDK has been modularized and we’ll demonstrate how the compiler and runtime behave in certain situations.
So what is a module?
Describing a module is actually quite simple – it is a unit of software that declares the answers to three questions about itself in a file named module-info.java:
- What is its name?
- What does it export?
- What does it require?
The answer to the first question is easy, (almost) each module is given a name. This name should be following something similar to the package convention, e.g. de.codecentric.mymodule, to avoid conflicts.
To answer the second question, modules provide a list of all the packages of this particular module that are considered public API and thus usable by other modules. If a class is not in an exported package, no one outside of your module can access it – even if it is public.
The third question is answered by a list of modules that this module depends on – all public types that those modules export are accessible by the module in question. The Jigsaw team is trying to establish the phrase “reading another module”.
This is a major shift from the status quo. Up to Java 8, every public type on your classpath is accessible by every other type. With Jigsaw, accessibility of Java types evolves from
- public
- private
- default
- protected
to
- public to everyone who reads this module (exports)
- public to some modules that read this module (exports to, this will be covered in part two)
- public to every other class within the module itself
- private
- protected
The modularized JDK
Module dependencies must form an acyclic graph, forbidding circular dependencies. To adhere to this principle, it was a major task for the Jigsaw team to modularize the Java Runtime which was reportedly full of circular and unintuitive dependencies. They came up with is this graph:
At the bottom of the graph resides java.base. This is the only module that only has inbound edges. Every module you create reads java.base whether you declare it or not – similar to the implied extension of java.lang.Object. java.base exports packages such as java.lang, java.util, java.math etc.
The modularization of the JDK means that you can now specify which modules of the Java Runtime you want to use – your application does not need to run in an environment that supports Swing or Corba if you do not read the java.desktop or java.corba modules. How to create such a stripped down environment will be shown in part two.
But enough with the dry stuff…
Let’s get hacking
All of the code that follows is available here , including the shell scripts for compiling, packaging and running an example.
My basic use case is very simple. I have a module named de.codecentric.zipvalidator that performs some kind of zip code validation. This module is read by the module de.codecentric.addresschecker (which could check more things than the zip code, but doesn’t to keep things lean).
The zipvalidator is specified by the following module-info.java:
module de.codecentric.zipvalidator{
exports de.codecentric.zipvalidator.api;
}
So this module exports the de.codecentric.zipvalidator.api package and does not read any other module (except for java.base). This module is read by the addresschecker:
module de.codecentric.addresschecker{
exports de.codecentric.addresschecker.api;
requires de.codecentric.zipvalidator;
}
The overall file system structure is
two-modules-ok/
├── de.codecentric.addresschecker
│ ├── de
│ │ └── codecentric
│ │ └── addresschecker
│ │ ├── api
│ │ │ ├── AddressChecker.java
│ │ │ └── Run.java
│ │ └── internal
│ │ └── AddressCheckerImpl.java
│ └── module-info.java
├── de.codecentric.zipvalidator
│ ├── de
│ │ └── codecentric
│ │ └── zipvalidator
│ │ ├── api
│ │ │ ├── ZipCodeValidator.java
│ │ │ └── ZipCodeValidatorFactory.java
│ │ ├── internal
│ │ │ └── ZipCodeValidatorImpl.java
│ │ └── model
│ └── module-info.java
By convention, modules are placed in a folder that shares the same name as the module.
For the first example, everything looks fine – we’re extremely well-behaved and only access the ZipCodeValidator and ZipCodeValidatorFactory from the exported package in our AddressCheckerImpl class:
1public class AddressCheckerImpl implements AddressChecker {
2 @Override
3 public boolean checkZipCode(String zipCode) {
4 return ZipCodeValidatorFactory.getInstance().zipCodeIsValid(zipCode);
5 }
6}
So let’s fire up javac and generate some bytecode. To compile the zipvalidator (which we need to do first of course as the addresschecker reads the zipvalidator), we issue
1javac -d de.codecentric.zipvalidator \ 2$(find de.codecentric.zipvalidator -name "*.java")
This looks familiar – there is no mention of modules yet as the zipvalidator does not depend on any custom module. The find just helps us with listing .java files within the given folder.
But how do we tell javac about our module structure when we want to compile? For this, Jigsaw introduces the switch -modulepath or -mp.
To compile the addresschecker, we use the following command:
1javac -modulepath . -d de.codecentric.addresschecker \ 2$(find de.codecentric.addresschecker -name "*.java")
By using the modulepath, we tell javac where to find the compiled modules (in this case, this is .), so this is somewhat similar to the classpath switch.
Compiling multiple modules separately seems like a hassle though – we can make use of another switch called -modulesourcepath to compile multiple modules at once:
1javac -d . -modulesourcepath . $(find . -name "*.java")
This searches all subdirectories of . for module folders and compiles all java files within.
Once we have compiled everything, we naturally want to give it a go:
1java -mp . -m de.codecentric.addresschecker/de.codecentric.addresschecker.api.Run 76185
Once again we specify a modulepath so that the JVM knows where to find the compiled modules. We also specify a main class (and a parameter).
Hooray, the output is
76185 is a valid zip code
Lucky us!
Modular Jars
Of course, in the Java world we are used to receive and deliver our bytecode in jar files. Jigsaw introduces the concept of the modular jar. A modular jar is very similar to a regular jar, but it also contains a compiled module-info.class. Providing they’re compiled for the right target version, these jars will be downward compatible. module-info.java is not a valid type name, so a compiled module-info.class will be ignored by older JVMs.
To build a jar for the zipvalidator, we issue
1jar --create --file bin/zipvalidator.jar \ 2--module-version=1.0 -C de.codecentric.zipvalidator .
We specify an output file, a version (though there is no specific notion of using multiple versions of a module in Jigsaw at runtime) and the module to package.
As the addresschecker also has a main class, we can specify this as well:
1jar --create --file=bin/addresschecker.jar --module-version=1.0 \ 2--main-class=de.codecentric.addresschecker.api.Run \ 3-C de.codecentric.addresschecker .
The main class is not being specified in module-info.java as one might expect (and as was initially planned by the Jigsaw team) but written into the Manifest as usual.
Running this example with
1java -mp bin -m de.codecentric.addresschecker 76185
results in the same answer as before. We again specify the modulepath which in this example is the bin folder which we wrote our jars to. We do not need to specify a main class as the Manifest of addresschecker.jar already contains this information. Providing the module name to the -m switch is sufficient.
Until now, everything has been well and dandy. Now we start tinkering with the modules a bit to see how Jigsaw behaves at compile and runtime when you’re not being a good {girl|boy}.
Using non exported types
In this example, we’ll see what happens when we access a type from another module that we shouldn’t use.
Since we’re bored of this factory thing in AddressCheckerImpl, we’ll change the implementation to
1return new ZipCodeValidatorImpl().zipCodeIsValid(zipCode);
Trying to compile this results in a well-earned
1error: ZipCodeValidatorImpl is not visible because 2package de.codecentric.zipvalidator.internal is not visible
So directly using non-exported types fails at compile time.
Feeling smart, we try to be a bit more sneaky and use reflection.
1ClassLoader classLoader = AddressCheckerImpl.class.getClassLoader(); 2try { 3 Class aClass = classLoader.loadClass("de.[..].internal.ZipCodeValidatorImpl"); 4 return ((ZipCodeValidator)aClass.newInstance()).zipCodeIsValid(zipCode); 5} catch (Exception e) { 6 throw new RuntimeException(e); 7}
This compiles just fine, so let’s run it. Ah, Jigsaw is not fooled that easily:
1java.lang.IllegalAccessException: 2class de.codecentric.addresschecker.internal.AddressCheckerImpl 3(in module de.codecentric.addresschecker) cannot access class [..].internal.ZipCodeValidatorImpl 4(in module de.codecentric.zipvalidator) because module 5de.codecentric.zipvalidator does not export package 6de.codecentric.zipvalidator.internal to module 7de.codecentric.addresschecker
So Jigsaw does not only include compile-time checks, but also runtime checks! And it’s very explicit about what we did wrong, too.
Circular dependencies
In the next case, we have suddenly realized that the addresschecker module contains a class in its API that the zipvalidator would very much like to use. Since we’re lazy, instead of refactoring the class to another module, we declare a dependency to the addresschecker:
module de.codecentric.zipvalidator{
requires de.codecentric.addresschecker;
exports de.codecentric.zipvalidator.api;
}
As cyclic dependencies are not allowed by definition, the compiler stands in the way of our happiness (but for the common good):
1./de.codecentric.zipvalidator/module-info.java:2: 2error: cyclic dependence involving de.codecentric.addresschecker
We cannot do this and are made aware of the problem early at compile time.
Implied readability
To provide some more functionality, we decide to extend the zipvalidator by introducing a new module de.codecentric.zipvalidator.model that contains some kind of model for the validation result instead of using a boring boolean. The new file structure is shown here:
three-modules-ok/
├── de.codecentric.addresschecker
│ ├── de
│ │ └── codecentric
│ │ └── addresschecker
│ │ ├── api
│ │ │ ├── AddressChecker.java
│ │ │ └── Run.java
│ │ └── internal
│ │ └── AddressCheckerImpl.java
│ └── module-info.java
├── de.codecentric.zipvalidator
│ ├── de
│ │ └── codecentric
│ │ └── zipvalidator
│ │ ├── api
│ │ │ ├── ZipCodeValidator.java
│ │ │ └── ZipCodeValidatorFactory.java
│ │ └── internal
│ │ └── ZipCodeValidatorImpl.java
│ └── module-info.java
├── de.codecentric.zipvalidator.model
│ ├── de
│ │ └── codecentric
│ │ └── zipvalidator
│ │ └── model
│ │ └── api
│ │ └── ZipCodeValidationResult.java
│ └── module-info.java
ZipCodeValidationResult is a simple enum that has instances such as “too short”, “too long” etc.
The module-info.java has been extended to this
module de.codecentric.zipvalidator{
exports de.codecentric.zipvalidator.api;
requires de.codecentric.zipvalidator.model;
}
Our implementation of ZipCodeValidator now looks like this
1@Override 2public <strong>ZipCodeValidationResult</strong> zipCodeIsValid(String zipCode) { 3 if (zipCode == null) { 4 return ZipCodeValidationResult.ZIP_CODE_NULL; 5[snip] 6 } else { 7 return ZipCodeValidationResult.OK; 8 } 9}
The addresschecker module has been adapted to work with this enum as return type as well, so we’re good to go, right? Wrong! Compiling results in
1./de.codecentric.addresschecker/de/[..]/internal/AddressCheckerImpl.java:5: 2error: ZipCodeValidationResult is not visible because package 3de.codecentric.zipvalidator.model.api is not visible
There is an error in the compilation of the addresschecker – the zipvalidator uses exported types from the zipvalidator model in its public API. Since the addresschecker does not read this module, it cannot access this type.
There are two solutions for this. The obvious one is adding a read edge from the addresschecker to the zipvalidator model. This is however a slippery slope – why would we need to declare this dependency if we only need it for using the zipvalidator? Shouldn’t the zipvalidator ensure that we can access all required modules? It should and it can – welcome to implied readability. By adding the keyword public to the requires definition, we tell all client modules that they also need to read another module. As an example, this is the updated module-info.java of the zipvalidator:
module de.codecentric.zipvalidator{
exports de.codecentric.zipvalidator.api;
requires public de.codecentric.zipvalidator.model;
}
The public keyword tells all modules that read the zipvalidator that they also need to read the zipvalidator model. This is a change of what you’re used to with the classpath – you cannot rely on a Maven POM for example to ensure your dependencies are also available to any client, you have to specify them explicitly if they are part of your public API. This is a very nice model – if you use a dependency only internally, why should any of your clients be bothered with them? And if you use it externally, you should be open about it, too.
Summary and Outlook
This concludes part one. We talked about the three questions each module answers and the modularization of the Java Runtime. We proceeded with an example where we compiled, ran and packaged a simple Java application consisting of two modules. Then, we started tinkering with the working example to see how the module system behaves when its rules are violated. An extension of functionality then introduced a third module and the concept of implied readability.
The next part will cover the following topics:
- How does Jigsaw behave when there are multiple modules with the same name on the modulepath?
- What happens when there are modules on the modulepath that have different names but export the same packages?
- How do you work with legacy dependencies that are not modularized?
- How do you create your own stripped-down runtime image?
For more information about Jigsaw I recommend the Jigsaw Project home page , especially the slides and videos from this year’s JavaOne sessions and project lead Mark Reinhold’s essay State of the Module System .
More articles
fromFlorian Troßbach
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
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.
Blog author
Florian Troßbach
Senior IT Consultant
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.