Java 9: Modules, JShell, and a Smarter Platform
Java 9 introduced Project Jigsaw — the Java Platform Module System — fundamentally restructuring the JDK itself. It also brought JShell (the first REPL for Java), convenient collection factory methods, a new HTTP client, and a raft of API improvements.
Java Evolution — Part 2
Java 9, released on September 21, 2017, was the first version to ship after the new six-month release cadence was announced — though it was itself delayed several times. Its headline feature, Project Jigsaw, restructured the entire JDK into modules and gave developers a formal way to declare dependencies and encapsulation boundaries.
The Java Platform Module System (JPMS)
Before Java 9, the JDK was a monolithic runtime. Every application loaded the entire standard library, even if it only used a fraction of it. More critically, there was no enforced encapsulation at the package level — public meant accessible from anywhere, making it impossible to hide internal APIs.
Project Jigsaw (JEP 261) solved both problems by introducing the module as a first-class concept in the Java platform.
What Is a Module?
A module is a named, self-describing unit of code. It declares:
- What packages it exports (makes accessible to other modules)
- What other modules it requires (depends on)
- What services it provides or uses
This information lives in a file called module-info.java at the root of the module’s source tree.
// module-info.java for a hypothetical "com.myapp.core" modulemodule com.myapp.core { requires java.sql; // depends on the java.sql module requires transitive java.logging; // re-exports java.logging to consumers exports com.myapp.core.api; // only this package is public exports com.myapp.core.model to com.myapp.web; // qualified export opens com.myapp.core.internal to com.fasterxml.jackson.databind; // for reflection}The JDK Is Now Modular
The JDK itself was split into approximately 90 modules. The most important ones are:
| Module | Contents |
|---|---|
java.base | Core types: java.lang, java.util, java.io, java.nio |
java.sql | JDBC API |
java.logging | java.util.logging |
java.desktop | AWT, Swing |
java.xml | XML processing (JAXP) |
jdk.jshell | The JShell REPL tool |
Every module implicitly requires java.base, so you never need to declare that dependency.
Creating a Custom Runtime Image with jlink
Because the JDK is modular, you can now create a minimal runtime image containing only the modules your application actually needs:
jlink \ --module-path $JAVA_HOME/jmods \ --add-modules com.myapp.core,java.sql \ --output my-custom-runtimeThe resulting image can be 30–80% smaller than a full JDK, which is particularly valuable for containerised deployments.
Strong Encapsulation
With modules, public no longer means universally accessible. A type is only accessible from outside its module if the module explicitly exports the package containing it. This broke a number of frameworks and tools that relied on reflective access to JDK internals — the --add-opens flag was introduced as an escape hatch during the transition period.
JShell — Java’s First REPL
JShell (JEP 222) is an interactive Read-Eval-Print Loop (REPL) for Java. Before Java 9, experimenting with a snippet of Java code required creating a class, writing a main method, compiling, and running. JShell removes all of that friction.
$ jshell| Welcome to JShell -- Version 9| For an introduction type: /help intro
jshell> int x = 10x ==> 10
jshell> int y = 20y ==> 20
jshell> x + y$3 ==> 30
jshell> String greet(String name) { ...> return "Hello, " + name + "!"; ...> }| created method greet(String)
jshell> greet("Duke")$5 ==> "Hello, Duke!"
jshell> /list 1 : int x = 10; 2 : int y = 20; 3 : x + y 4 : String greet(String name) { return "Hello, " + name + "!"; } 5 : greet("Duke")JShell is invaluable for quickly testing API behaviour, exploring new features, and teaching Java without the ceremony of a full project.
Collection Factory Methods
Java 9 added convenient static factory methods to List, Set, and Map for creating small, immutable collections without the verbose Arrays.asList() or Collections.unmodifiableList() boilerplate.
// Before Java 9List<String> oldList = Collections.unmodifiableList( Arrays.asList("a", "b", "c"));
// Java 9List<String> list = List.of("a", "b", "c");Set<String> set = Set.of("x", "y", "z");Map<String, Integer> map = Map.of( "one", 1, "two", 2, "three", 3);
// For larger maps, use Map.ofEntriesMap<String, Integer> bigMap = Map.ofEntries( Map.entry("a", 1), Map.entry("b", 2), Map.entry("c", 3), Map.entry("d", 4));Important: Collections created with
List.of(),Set.of(), andMap.of()are truly immutable — any attempt to add, remove, or replace elements throwsUnsupportedOperationException. They also do not permitnullelements.
The New HTTP Client (Preview)
Java 9 introduced a new HttpClient API (JEP 110) as an incubating module (jdk.incubator.httpclient). It was designed to replace the old, cumbersome HttpURLConnection with a modern, fluent API supporting HTTP/2 and WebSockets. It became standard in Java 11 — we will cover it there in detail.
Stream API Enhancements
Java 9 added four useful methods to the Stream interface:
// takeWhile — take elements while predicate holds (stops at first failure)Stream.of(1, 2, 3, 4, 5, 1, 2) .takeWhile(n -> n < 4) .forEach(System.out::println); // 1, 2, 3
// dropWhile — drop elements while predicate holds, then take the restStream.of(1, 2, 3, 4, 5, 1, 2) .dropWhile(n -> n < 4) .forEach(System.out::println); // 4, 5, 1, 2
// iterate with a predicate (like a for-loop)Stream.iterate(1, n -> n <= 10, n -> n + 1) .forEach(System.out::println); // 1 through 10
// ofNullable — create a stream of 0 or 1 elementsStream.ofNullable(null).count(); // 0Stream.ofNullable("hello").count(); // 1Optional Enhancements
Optional gained three new methods in Java 9:
Optional<String> opt = Optional.empty();
// ifPresentOrElse — handle both present and absent casesopt.ifPresentOrElse( s -> System.out.println("Found: " + s), () -> System.out.println("Not found")); // Not found
// or — provide an alternative Optional if emptyOptional<String> result = opt.or(() -> Optional.of("default"));// Optional["default"]
// stream — convert Optional to a Stream (0 or 1 elements)long count = opt.stream().count(); // 0Private Interface Methods
Java 9 extended the interface evolution started in Java 8 by allowing private methods inside interfaces. This enables default methods to share common implementation logic without exposing that logic as part of the interface’s contract.
interface Logger { default void logInfo(String message) { log("INFO", message); }
default void logError(String message) { log("ERROR", message); }
// Private helper — not part of the public contract private void log(String level, String message) { System.out.printf("[%s] %s%n", level, message); }}Process API Improvements
Java 9 significantly enhanced the ProcessHandle API, making it easier to manage and inspect OS processes from Java code:
// Get information about the current processProcessHandle current = ProcessHandle.current();System.out.println("PID: " + current.pid());System.out.println("Command: " + current.info().command().orElse("unknown"));
// List all running processesProcessHandle.allProcesses() .filter(p -> p.info().command().isPresent()) .forEach(p -> System.out.println(p.pid() + " " + p.info().command().get()));
// Start a process and react when it finishesProcessBuilder pb = new ProcessBuilder("sleep", "5");Process process = pb.start();process.onExit().thenAccept(p -> System.out.println("Process " + p.pid() + " exited"));Summary
Java 9 was a structural release. Its most lasting impact was the module system, which changed how the JDK itself is organised and gave developers the tools to build well-encapsulated, deployable applications.
| Feature | Key Benefit |
|---|---|
| JPMS (Modules) | Strong encapsulation, reliable configuration, smaller runtimes |
| JShell | Interactive experimentation without boilerplate |
| Collection Factories | Concise, immutable collection creation |
| Stream Enhancements | takeWhile, dropWhile, iterate with predicate, ofNullable |
| Optional Enhancements | ifPresentOrElse, or, stream |
| Private Interface Methods | Shared logic in default methods without leaking internals |
| Process API | Better OS process management and inspection |