Java 9 and the Module System: What Developers Need to Know
Back to Blog

Java 9 and the Module System: What Developers Need to Know

6 min read
Read in Deutsch

Java 9 and the Module System: What Developers Need to Know

Since September 21st, it's official: Java 9 is here. After multiple delays -- it was originally planned for 2016 -- arguably the biggest update to the Java platform since the introduction of Generics is finally available. At its core: the Java Platform Module System (JPMS), better known by its project name Jigsaw.

In this article, we examine what the module system concretely means, how the new module-info.java works, and what to watch out for when migrating existing projects.

What Is Project Jigsaw?

The module system solves a problem that has accompanied Java since its inception: the lack of encapsulation at the package level. Until now, any class declared as public could be used from anywhere on the classpath -- even if it was intended as an internal implementation detail. Who hasn't used classes from sun.misc.* or com.sun.* at some point, even though they were never intended as public API?

JPMS introduces a new layer above packages: Modules. A module explicitly defines:

  • Which packages it makes visible to the outside (exports)
  • Which other modules it depends on (requires)
  • Which services it provides or consumes

With this, Java gains true encapsulation at the architectural level for the first time. The JDK library itself has been split into around 90 modules -- from java.base (which every module automatically imports) to java.sql to java.logging.

The module-info.java in Detail

Each module is described by a module-info.java file in the root directory of the source tree. The syntax is lean and readable:

module com.encircle360.backend {
    // Declare dependencies
    requires java.sql;
    requires java.logging;
    requires spring.core;
    requires spring.context;

    // Expose packages externally
    exports com.encircle360.backend.api;
    exports com.encircle360.backend.model;

    // Internal packages remain encapsulated -- no exports needed
    // com.encircle360.backend.internal is not visible from outside

    // Selectively allow reflection (e.g., for frameworks)
    opens com.encircle360.backend.model to spring.core;
}

An overview of the most important keywords:

  • requires -- Declares a dependency on another module. With requires transitive, the dependency is passed through to consuming modules.
  • exports -- Makes a package visible to other modules. Only exported packages are accessible from outside.
  • opens -- Allows reflection access to a package. This is particularly relevant for frameworks like Spring or Hibernate that rely heavily on reflection.
  • provides ... with -- Registers a service implementation for the ServiceLoader API.
  • uses -- Declares that the module consumes a particular service.

What Changes for Existing Projects?

The good news first: Java 9 is largely backward compatible. Existing code that does not use internal JDK APIs should run on Java 9 without changes in most cases. The so-called Unnamed Module ensures that code without a module-info.java continues to work -- everything on the classpath automatically ends up in this module and has access to all exported packages.

The less good news: anyone who has used internal JDK APIs will run into problems. sun.misc.Unsafe, sun.misc.BASE64Encoder, and similar classes are now encapsulated. The JVM still issues a warning rather than an error by default with --illegal-access=warn, but this is intended as a transitional measure. In future Java versions, access will be fully blocked.

Common Pitfalls During Migration

Split Packages: If two JARs contain classes in the same package, this no longer works in the module system. Each package may only be assigned to one module. In practice, this primarily affects projects that use older libraries with overlapping package names.

Reflection Access: Frameworks like Spring, Hibernate, or Jackson rely heavily on reflection. In the module system, reflection must be explicitly allowed via opens. If you want to open an entire module for reflection, you can use open module -- this is pragmatic but naturally a compromise on encapsulation.

Tooling and Build Systems: Maven and Gradle already offer basic support for Java 9 modules, but the integration is not yet mature everywhere. Especially with multi-module Maven projects, configuring the module path can be tricky.

Honest Assessment: Should You Migrate Now?

As of today -- just days after the release -- our recommendation is: Don't rush. Many popular libraries and frameworks do not yet offer full JPMS support. Spring Framework 5, which will officially support Java 9, has not yet been released as final. Hibernate, Jackson, Guava, and many other libraries are also still working on modularization.

For new greenfield projects, you can use Java 9 as a runtime and gradually introduce modules. For existing projects, we recommend:

  1. Update the runtime only first -- Use Java 9 as the runtime environment without defining your own modules
  2. Check your dependencies -- Use jdeps to analyze which internal APIs are being used
  3. Eliminate internal API usage -- Replace sun.misc.BASE64Encoder with java.util.Base64 and perform similar cleanup
  4. Introduce modules gradually -- When all dependencies are ready

Other Highlights in Java 9

Besides the module system, Java 9 brings several other interesting features:

JShell (REPL): Java finally has an interactive shell. With jshell, you can try out Java code directly on the command line -- ideal for rapid prototyping and learning.

Private Methods in Interfaces: Since Java 8, interfaces can have default methods. Java 9 now also allows private methods in interfaces, improving code reuse within interface definitions.

Stream API Improvements: New methods like takeWhile(), dropWhile(), and ofNullable() make working with Streams more flexible.

HTTP/2 Client (Incubator): A new HTTP client with support for HTTP/2 and WebSockets is included as an incubator module. It is intended to eventually replace the aging HttpURLConnection.

Collection Factory Methods: Immutable collections can now be elegantly created: List.of("a", "b", "c"), Set.of(1, 2, 3), and Map.of("key", "value").

Conclusion

Java 9 is a significant release, and the module system will fundamentally change the way we structure Java applications in the long run. The benefits -- true encapsulation, clear dependencies, smaller runtime images through jlink -- are real and relevant.

At the same time, migration is not a walk in the park. The ecosystem needs time to catch up. Those who evaluate Java 9 now and prepare their codebase will find the transition much easier once the library landscape is ready.

At encircle360, we are actively supporting this transition and will report on our experiences migrating specific projects in future articles. If you have questions about Java 9 migration, feel free to reach out to us.

Patrick Hütter

Written by

Patrick Hütter

Founder & Software Architect

Software architect, engineer and entrepreneur. Patrick has been building products and platforms for over a decade — from enterprise backends and cloud-native infrastructure to AI-powered applications. As founder of encircle360, he combines deep technical expertise with entrepreneurial vision, driving open source projects that create real impact.