Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Project Jigsaw Is Coming

DZone's Guide to

Project Jigsaw Is Coming

Jump feet first into Project Jigsaw, a new tool in Java 9 that helps make your code more modular.

· Java Zone
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

This article is featured in the new DZone Guide to Modern Java, Volume II. Get your free copy for more insightful articles, industry statistics, and more. 

Java 9 is coming! It’s just nine more months to its release, so this is a good time to familiarize ourselves with it. There are a couple of interesting new features, like Java’s REPL (a.k.a. JShell), the additions to the Stream and Optional APIs, support for HTTP 2.0, and more. But they are all overshadowed by Java 9’s flagship feature: Project Jigsaw, which will bring artifact-level modularity to the language. Let’s have a look at it! (All unattributed quotes come from the excellent State of the Module System.)

Creating Modules

In Java 9 we will have the possibility to create modules. But what exactly is a module? It’s much like a regular artifact (most commonly a JAR) but it has three additional properties, which it explicitly expresses:

  • a name

  • dependencies

  • a well-defined API

This information is encoded in a module descriptor (in the form of module-info.class), which is compiled from a module declaration (module-info.java). The module descriptor defines the three properties stated above and we’ll see in a minute how it does that.

After creating the module-info.java we (and in the future, our tools) have to compile it to module-info.class and then package it together with the rest of our source files. The result is a modular JAR. At runtime, the JVM will read the classes and resources along with module-info.class and turn it all into a module.

But how does the module declaration work, and how do the compiler and JVM interpret it? (By the way, I assume reading “compiler and JVM” is as tiring as writing it, so I will forego some precision and use “Java” instead.)

Module Declaration

As stated above, a module declaration defines a module’s name, dependencies, and API. It is usually defined in module-info.java and looks like this:

module MODULE_NAME {
    requires OTHER_MODULE_NAME;
    requires YET_ANOTHER_MODULE_NAME;
    exports PACKAGE_NAME;
    exports OTHER_PACKAGE_NAME;
    exports YET_ANOTHER_PACKAGE_NAME;
}


Let’s examine the three properties one by one.

Name

A module’s name can be arbitrary, but to ensure uniqueness, it is recommended to stick with the inverse-URL naming schema for packages. Guava, for example, will likely be com.google.guava, and Apache Commons IO could be either org.apache.commons.commons_io or org.apache.commons.io.

While this is not necessary, it will often lead to the module name being a prefix of the packages it contains.

Dependencies

A module lists the other modules it depends on to compile and run by naming them in requires clauses. This is true for application and library modules, but also for modules in the JDK itself, which was split up into about 80 of them (have a look at them with java -listmods).

Java 9 brings modularity to the language. Modules are like JARs but come with a descriptor that defines a name, dependencies, and an API. Two basic rules, readability and accessibility, build on that and allow reliable configuration, strong encapsulation, improved performance, security, maintenance, and more.

Migration will not be without challenges and should be well prepared.

API

By default, all types are internal to the module and not visible outside of it. But surely some types should be visible to the outside, right? Yes, and this is done by exporting the packages that contain these types with the exports clause. Other code, and even reflection, can only access public types in exported packages.

JAR Hell and Other Niceties

That is all nice and dandy but… why do we need it? Well, there are some deep-seated problems with how Java handles artifacts. Before Project Jigsaw, Java sees JARs as simple containers for compiled classes without any meaningful representation at compile or run time. It simply rips all of the classes it has to access out of their JARs and rolls them into one big ball of mud. The JARs themselves, left behind on the class path, are meaningless, and it does not matter at all how many there are and how they are structured. For all Java cares, there might just as well only be a single JAR.

This has a couple of negative consequences, and some of them are notable contributors to JAR Hell:

  • Dependencies between artifacts can not be expressed, which means that many problems lead to runtime errors and crashing applications (NoClassDefFoundError anyone?).

  • Loading classes requires linear scans of the class path.

  • If there are several classes with the same fully qualified name, the first one found during the scan will be loaded and will shadow the others.

  • There is no way to reliably run an application that depends on two versions of the same library (usually as transitive dependencies via other libraries it needs).

  • There is no way to have code that is only visible inside a JAR.

  • Security-relevant code cannot be made accessible to some JARs but hidden from others.

These problems can cause all kinds of trouble, like bad performance, lacking security, maintenance nightmares, and anything from too-subtle-to-notice misbehavior to havoc-wreaking errors. Hence, lots of tools and mechanisms were devised to tackle some problem or other from our list: build tools, web servers, component systems, Java’s very own security manager, fiddling with class loaders, and so on. These generally work, but not without adding their own complexity and potential for errors. Sometimes considerable amounts of it!

It would be so much better if Java itself would have an understanding of artifacts…

Modularity With Project Jigsaw

Enter Project Jigsaw! It was specifically designed to provide a solution to these problems. At the core of that solution are the modules that we already looked at and three other concepts:

  • module graph

  • readability

  • accessibility

Together they want to achieve the project’s goals, most notably among them:

  • reliable configuration

  • strong encapsulation

  • improved security, maintainability, and performance

Module Graph

The information contained in module descriptors gives Java the ability to actually understand what’s going on between modules. So, instead of the big ball of mud it created before, it can now map how they relate to each other. More precisely, it builds a graph where the modules are nodes and where the dependencies (expressed by the requires clauses) are edges. Fittingly, this is called the Module Graph. 

Readability

Directly based on this graph is the concept of Readability:

When one module depends directly upon another in the module graph, then code in the first module will be able to refer to types in the second module. We therefore say that the first module reads the second or, equivalently, that the second module is readable by the first.

Reliable Configuration

Readability is the basis of reliable configuration:

The readability relationships defined in a module graph are the basis of reliable configuration: The module system ensures that every dependence is fulfilled by precisely one other module, that the module graph is acyclic, that every module reads at most one module defining a given package, and that modules defining identically-named packages do not interfere with each other. So expressing and understanding dependencies means that a lot of the problems that used to crash an application can now be found at launch or even compile time!

Improved Performance

Readability also helps to improve performance. The module system now knows for any given class which module is supposed to contain it and can thus forego the repeated linear scans. And with clearer bounds of where code is used, existing byte-code optimization techniques can be used more effectively.

As JSR 376 puts it:

Many ahead-of-time, whole-program optimization techniques can be more effective when it is known that a class can refer only to classes in a few other specific components rather than to any class loaded at run time. It might also be possible to index annotated classes so that they can be found without a full class path scan.

Accessibility

Together with readability, the exports clauses are the basis for Accessibility:

The Java compiler and virtual machine consider the public types in a package in one module to be accessible by code in some other module only when the first module is readable by the second module, in the sense defined above, and the first module exports that package.

Strong Encapsulation

What happens if code tries to access a type that does not fulfill these requirements? A type referenced across module boundaries that is not accessible in this way is unusable in the same way that a private method or field is unusable: Any attempt to use it will cause an error to be reported by the compiler, or an IllegalAccessError to be thrown by the Java virtual machine, or an IllegalAccessException to be thrown by the reflective run-time APIs. Thus, even when a type is declared public, if its package is not exported in the declaration of its module then it will only be accessible to code in that module.

This means that public is no longer public! It also means that modules will be able to hide their internals and clearly define the parts of their functionality that make up their public API. Mark Reinhold, spec lead on Project Jigsaw, once wrote about this:

A class that is private to a module should be private in exactly the same way that a private field is private to a class.

It might take some time to get used to “public” no longer meaning “public for everyone” but “public for everyone in this module.” I am convinced that this is worth it, though, as it finally allows us to create a safe zone within a module.

Improved Security and Maintainability

The strong encapsulation of module-internal APIs greatly improves security and maintainability. It helps with security because critical code is now effectively hidden from code which does not require to use it. It makes maintenance easier as a module’s public API can more easily be kept small.

Birth Pains

But not all is well…

Migration Challenges

Besides the core features we just discussed, Jigsaw entails a lot of changes under the hood. While almost all of them are backwards compatible in the strict meaning of the word, some interact badly with existing code bases. In the end, whether you modularize your application or not, running on Java 9 may break your code.

A couple of things that could cause trouble:

  • Your (or, more likely, your dependencies) could depend on the JDK-internal API, which will soon be inaccessible thanks to strong encapsulation. See JEP 260 for details about which APIs will disappear. Also, have a look at jdeps and run jdeps -jdkinternals <jars> on your and your dependencies’ artifacts.

  • The JVM will refuse to launch when two modules contain packages with the same name (known as split package). If a module and a regular JAR split a package, the content of the JAR’s package would not be visible at all. 

  • The JRE/JDK layout changed:

    • rt.jar and tools.jar no longer exist

    • JDK modules are packed as JMODs, a new and deliberately unspecified format

    • there is no longer a folder jre in the JDK

    • the URLs for runtime content look different

  • Class loaders are no longer always URLClassLoader-s, so casts to that type will fail.

  • The Endorsed Standards Override Mechanism, Extension Mechanism, and Boot Class Path Override are gone.

More possible problems are listed under Risks and Assumptions in JEP 261.

What About Version Conflicts?

Unfortunately, the module system has no understanding of versions. It will see two different versions of the same module as a duplication and refuse to compile or launch. The fact that the module system does nothing to ameliorate version conflicts is, frankly, somewhat disappointing, and I believe we might soon be talking about module hell.

Contested Topics

These are some of the questions currently being discussed on the Jigsaw mailing list:

  • Should strong encapsulation be stronger than reflection?

  • Do we need optional dependencies?

  • Should modules be able to “stand in” for other modules (by aliasing)?

  • Is it helpful that a module can make its dependencies available to other modules? 

If these open questions or other details of Jigsaw interest you, make sure to check the mailing list archives or even participate in the discussion.

Summary

The most important takeaway is that Jigsaw introduces modules: JARs with a module descriptor that gives them names, explicit dependencies, and a well-defined API. The module graph, readability, and accessibility build on these descriptors to tackle JAR hell and other existing problems as well as to provide reliable configuration, strong encapsulation, and improved security, maintainability, and performance. But Jigsaw will make some migrations to Java 9 daunting and is also being criticized for a number of shortcomings — perceived or real.

To see for yourself, download JDK 9, play around with it — maybe by following my hands-on guide — and try to judge or even spike your code’s migration.

Download Modern Java EE Design Patterns: Building Scalable Architecture for Sustainable Enterprise Development.  Brought to you in partnership with Red Hat

Topics:
java ,project jigsaw ,modularity ,performance

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}