Few new Features of Java 9

Mohit jainMohit jain
7 min read

🔹 Strong Encapsulation

Before Java 9:

  • If a class was public, anyone on the classpath could use it — even if it was meant to be internal.

  • Libraries often had “public by accident” APIs because Java had no way to hide internals beyond package-private.

  • Even JDK internals (like sun.misc.Unsafe) were widely used by accident.

With the Java Platform Module System (JPMS):

  • Each module declares in module-info.java which packages it exports.

  • Only exported packages are accessible outside the module.
    Example:

      module com.example.foo {
          exports com.example.foo.api;   // visible to other modules
          // no export for com.example.foo.internal → hidden
      }
    
  • Everything else remains strongly encapsulated — you can’t import or reflectively access it without explicit opens.

  • The JDK itself is modularized: internal packages are strongly hidden unless you use --add-opens at runtime.

Benefit: You can design a clear API surface (what’s public) while keeping implementation details hidden.

🔹 What is module-info.java?

  • It’s a special descriptor file that lives in the root of a Java module (like src/main/java/module-info.java).

  • Think of it as the manifest for a module: it declares the module’s name, its dependencies, and what packages it makes available to the outside world.

Example:

module com.yahoo.identity.umapi {
    requires java.sql;              // depends on JDK module
    requires com.fasterxml.jackson; // depends on another module

    exports com.yahoo.identity.umapi.services;   // make this package visible to others
    exports com.yahoo.identity.umapi.providers;  // make this package visible to others
}

🔹 Why was it introduced?

Before Java 9:

  • All classes were thrown onto the classpath.

  • No strong boundaries — any code could access any public class, even internal ones.

  • JDK itself was a giant monolith (rt.jar).

With modules:

  • Stronger encapsulation: Only exported packages are visible to other modules.

  • Reliable configuration: Compiler/runtime knows exact dependencies.

  • Smaller runtimes: Tools like jlink can assemble minimal JREs with just the modules you need.

  • Better readability: The graph of dependencies is explicit.


🔹 Structure of module-info.java

  1. Module declaration

     module com.example.myapp { }
    
  2. Requires (dependencies)

     requires java.logging;
     requires com.fasterxml.jackson.databind;
    
  3. Exports (make packages visible to other modules)

     exports com.example.myapp.api;   // open to everyone
    
  4. Opens (for reflection — e.g., Hibernate/Jackson)

     opens com.example.myapp.model;   // allow deep reflection on this package
    
  5. Permits sealed types across packages

    • Without modules, sealed subclasses must be in the same package.

    • With modules, sealed subclasses just need to be in the same module.
      That’s why module-info.java matters for your case (JaxRsService sealed interface + RestEasyProvider).

🔹 Benefits in large projects

  • Helps prevent “classpath hell” (accidental collisions).

  • Makes APIs cleaner by hiding internal packages (only export what you want public).

  • Lets you ship smaller, faster JREs tuned for microservices (jlink).


  • jlink (introduced in Java 9) is a tool for creating a custom Java runtime image that contains:

    • only the modules your application actually needs,

    • a slimmed-down JVM,

    • and an optional launcher script for your app.

Instead of shipping a giant JDK/JRE (hundreds of MBs, with lots of unused modules), you ship a trimmed runtime (sometimes as small as ~30 MB).


🔹 Why It Matters

  1. Smaller Footprint

    • Ideal for Docker/microservices: reduces image size, faster deployments.

    • Example: If your app doesn’t need java.desktop, you leave it out.

  2. Security

    • Less surface area → fewer unneeded APIs available for attackers.

    • Only exports the modules you explicitly added.

  3. Performance

    • Startup can be faster because there are fewer modules to scan and initialize.
  4. Deployment Simplicity

    • You don’t depend on whatever JRE is installed on the server.

    • The runtime is self-contained and predictable.


🔹 How It Works (Step by Step)

1. Create/compile your modular JAR

Your app should have a module-info.java. Example:

module com.example.myapp {
    requires java.sql;
    exports com.example.myapp.api;
}

Compile:

javac -d out --module-source-path src $(find src -name "*.java")
jar --create --file myapp.jar -C out/com.example.myapp .

2. Analyze dependencies

Use jdeps to find what modules your app really uses:

jdeps --print-module-deps myapp.jar
# Example output: java.base,java.sql
jlink \
  --module-path $JAVA_HOME/jmods:out \
  --add-modules com.example.myapp,java.base,java.sql \
  --launcher runapp=com.example.myapp/com.example.myapp.Main \
  --output myruntime \
  --strip-debug --compress=2 --no-header-files --no-man-pages
  • --module-path → location of JDK modules + your app modules

  • --add-modules → modules to include

  • --launcher → creates a startup script

  • --output → target folder

  • Options like --strip-debug, --no-man-pages, --compress=2 shrink size further

Let’s breakdown this above code.
Here’s what every piece does, top to bottom:

jlink \

Runs the jlink tool to build a custom Java runtime image (a trimmed JRE containing only the modules you ask for).

  --module-path $JAVA_HOME/jmods:out \

Tells jlink where to find modules:

  • $JAVA_HOME/jmods → all standard JDK modules as .jmod files.

  • out → your app’s modules (modular JARs or .jmod you built).
    (On Windows use ; instead of :.)

  • 👉 But: this only makes them available. It doesn’t mean jlink will include everything in those paths.

  --add-modules com.example.myapp,java.base,java.sql \

Declares the root modules to include:

  • com.example.myapp → your application module.

  • java.base → the core module (implicitly required; listing it is fine).

  • java.sql → adds JDBC APIs.
    jlink will pull in transitive dependencies of these automatically; anything not needed is left out.

Note:- Understand in below way.

  • Think of --module-path as “where to look”,

  • and --add-modules as “which ones to pick up and package”.

  --launcher runapp=com.example.myapp/com.example.myapp.Main \

Creates a convenience launcher script inside the image:

  • Name: runapp (will appear under myruntime/bin/runapp or runapp.bat on Windows).

  • Target: run com.example.myapp.Main as the main class in the module com.example.myapp.
    So you’ll start your app with myruntime/bin/runapp instead of typing a long java -m ... command.

  --output myruntime \

Writes the generated runtime image to the myruntime/ directory:

myruntime/
  bin/        # contains 'java' and your 'runapp' launcher
  conf/
  lib/
  release
  --strip-debug --compress=2 --no-header-files --no-man-pages

Size/footprint optimizations:

  • --strip-debug → remove most debug metadata from classes in the image to shrink size (keep off if you need full debugging info in the image).

  • --compress=2 → compress resources in the image with the highest built-in level (smaller image; a tiny decompression cost at startup).

  • --no-header-files → don’t include C header files.

  • --no-man-pages → don’t include manual pages.
    (All of these reduce disk size; they don’t change your code’s runtime throughput.)

4. Run your custom runtime

./myruntime/bin/java -version
./myruntime/bin/runapp

You’ve now got a standalone runtime just for your app.


  • jmod → packaging format (like a JAR but module-aware, can contain native code/resources). The JDK itself ships as .jmod files under $JAVA_HOME/jmods.

  • jlink → the builder tool that uses .jmod files to assemble a runtime.

You usually use jlink. You’d touch jmod only if you’re distributing libraries or a custom module with native code.


🔹 Real-World Uses

  • Microservices: each container gets a stripped-down runtime with only java.base + app modules.

  • Desktop apps: ship one folder that has your app + a minimal JVM, no need for users to install Java.

  • Edge devices/IoT: very small runtimes possible (tens of MBs).


More about jmod :-

jmod is a file format introduced with Java 9 as part of the Java Platform Module System (JPMS). It packages a Java module into a .jmod file. You can think of it as a special archive format (like .jar files) but designed specifically for modules in the modular Java system.


Key Differences Between .jar and .jmod

  • .jar (Java Archive): Used to package classes and resources into a single file. It can be executed if it has a Main-Class.

  • .jmod (Java Module): Goes further by supporting native code, configuration files, and legal notices in addition to classes and resources. It is not executable directly.

So, while JARs are more general-purpose, JMODs are tailored for modular applications in Java 9+.


What’s Inside a .jmod File?

A .jmod file may contain:

  1. Compiled .class files (like a .jar).

  2. Native libraries (.dll, .so, .dylib).

  3. Configuration files.

  4. Legal/license notices.

  5. Resource files (images, text, etc.).

0
Subscribe to my newsletter

Read articles from Mohit jain directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mohit jain
Mohit jain

Oracle‬‭ Certified‬‭ Java‬‭ Developer‬‭ with‬‭ 7+ years‬‭ of‬‭ experience‬‭ specializing‬‭ in‬‭ backend‬‭ development,‬‭ microservices‬ architecture,‬‭ and‬‭ cloud-based‬‭ solutions.‬‭ Proven‬‭ expertise‬‭ in‬‭ designing‬‭ scalable‬‭ systems,‬‭ optimizing‬‭ performance,‬‭ and‬ mentoring‬‭ teams‬‭ to‬‭ enhance‬‭ productivity.‬‭ Passionate‬‭ about‬‭ building‬‭ high-performance‬‭ applications‬‭ using‬‭ Java,‬‭ Spring‬ Boot, Kafka, and cloud technologies (AWS/GCP)