Java 9 APIs & Features

Java 9 marked the end of feature-oriented releases, focusing instead on modularity and improved developer experience. It introduced JPMS for better application structure, safer memory access with VarHandles, and JShell for interactive development.

Released: September 2017
Standard Release

Java 9 Overview

Release Information

Release Date:

September 2017

Support Type:
Standard Release

Quick Summary

Java 9 marked the end of feature-oriented releases, focusing instead on modularity and improved developer experience. It introduced JPMS for better application structure, safer memory access with VarHandles, and JShell for interactive development.

Top Features

  • Project Jigsaw (Java Platform Module System)
  • VarHandles for safer memory access
  • JShell REPL (Read-Eval-Print-Loop) environment
  • G1 as Default Garbage Collector
  • StackWalking API for better stack trace handling
  • Improved Process API

API Examples

VarHandles

Safer alternative to sun.misc.Unsafe for memory operations

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class VarHandleExample {
    private volatile long counter = 0;
    private static final VarHandle COUNTER;
    
    static {
        try {
            COUNTER = MethodHandles.lookup()
                .findVarHandle(VarHandleExample.class, "counter", long.class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public void increment() {
        COUNTER.getAndAdd(this, 1L);
    }
    
    public long get() {
        return (long) COUNTER.get(this);
    }
}

JShell REPL

Interactive Java shell for experimentation

// Start JShell from command line: jshell

// Interactive commands:
jshell> int x = 42
x ==> 42

jshell> String greeting = "Hello, World!"
greeting ==> "Hello, World!"

jshell> System.out.println(greeting)
Hello, World!

jshell> Math.sqrt(16)
$3 ==> 4.0

// Define methods interactively
jshell> int factorial(int n) {
   ...>     return n <= 1 ? 1 : n * factorial(n-1);
   ...> }
|  created method factorial(int)

Java 9 Try-With-Resources Enhancement

Java 9 allows effectively final variables to be used in try-with-resources statements, eliminating the need to declare resources as final or create separate variables.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

// Java 8 approach - requires final or effectively final
public void readFileJava8(String fileName) throws IOException {
    BufferedReader reader = new BufferedReader(new FileReader(fileName));
    try (BufferedReader r = reader) {
        String line = r.readLine();
        System.out.println(line);
    }
}

// Java 9 approach - can use effectively final variables directly
public void readFileJava9(String fileName) throws IOException {
    BufferedReader reader = new BufferedReader(new FileReader(fileName));
    try (reader) {  // Direct use of the variable
        String line = reader.readLine();
        System.out.println(line);
    }
}

// Example with multiple resources
public void processFiles(String file1, String file2) throws IOException {
    BufferedReader reader1 = new BufferedReader(new FileReader(file1));
    BufferedReader reader2 = new BufferedReader(new FileReader(file2));
    
    try (reader1; reader2) {  // Multiple resources in one try-with-resources
        String line1 = reader1.readLine();
        String line2 = reader2.readLine();
        System.out.println("File1: " + line1);
        System.out.println("File2: " + line2);
    }
}

// Output example:
// File1: Hello from file1
// File2: Hello from file2

Java 9 Reactive Streams

Java 9 introduces Reactive Streams API for asynchronous stream processing with non-blocking backpressure. This enables efficient handling of data streams with controlled flow between producers and consumers, preventing memory overflow and ensuring optimal performance.

import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

// Custom Subscriber implementation
class MySubscriber<T> implements Flow.Subscriber<T> {
    private Flow.Subscription subscription;
    private String name;
    
    public MySubscriber(String name) {
        this.name = name;
    }
    
    @Override
    public void onSubscribe(Flow.Subscription subscription) {
        this.subscription = subscription;
        System.out.println(name + " subscribed");
        subscription.request(1); // Request first item
    }
    
    @Override
    public void onNext(T item) {
        System.out.println(name + " received: " + item);
        subscription.request(1); // Request next item
    }
    
    @Override
    public void onError(Throwable throwable) {
        System.out.println(name + " error: " + throwable.getMessage());
    }
    
    @Override
    public void onComplete() {
        System.out.println(name + " completed");
    }
}

// Example usage
public class ReactiveStreamsExample {
    public static void main(String[] args) throws InterruptedException {
        // Create a publisher
        SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
        
        // Create subscribers
        MySubscriber<String> subscriber1 = new MySubscriber<>("Subscriber1");
        MySubscriber<String> subscriber2 = new MySubscriber<>("Subscriber2");
        
        // Subscribe
        publisher.subscribe(subscriber1);
        publisher.subscribe(subscriber2);
        
        // Publish items
        publisher.submit("Item 1");
        publisher.submit("Item 2");
        publisher.submit("Item 3");
        
        // Close publisher
        publisher.close();
        
        // Wait for completion
        Thread.sleep(1000);
    }
}

// Output:
// Subscriber1 subscribed
// Subscriber2 subscribed
// Subscriber1 received: Item 1
// Subscriber2 received: Item 1
// Subscriber1 received: Item 2
// Subscriber2 received: Item 2
// Subscriber1 received: Item 3
// Subscriber2 received: Item 3
// Subscriber1 completed
// Subscriber2 completed

Collection Improvements

Java 9 introduces factory methods for creating immutable collections (List, Set, Map) with concise syntax. These methods provide a convenient way to create small, unmodifiable collections with predefined elements, improving code readability and reducing boilerplate.

import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.Map.Entry;

public class CollectionImprovementsExample {
    public static void main(String[] args) {
        // Immutable List creation
        List<String> immutableList = List.of("Apple", "Banana", "Cherry");
        System.out.println("Immutable List: " + immutableList);
        
        // Immutable Set creation
        Set<String> immutableSet = Set.of("Red", "Green", "Blue");
        System.out.println("Immutable Set: " + immutableSet);
        
        // Immutable Map creation
        Map<String, Integer> immutableMap = Map.of(
            "One", 1,
            "Two", 2,
            "Three", 3
        );
        System.out.println("Immutable Map: " + immutableMap);
        
        // Map with more than 10 entries using Map.ofEntries
        Map<String, Integer> largeMap = Map.ofEntries(
            Map.entry("A", 1),
            Map.entry("B", 2),
            Map.entry("C", 3),
            Map.entry("D", 4),
            Map.entry("E", 5),
            Map.entry("F", 6),
            Map.entry("G", 7),
            Map.entry("H", 8),
            Map.entry("I", 9),
            Map.entry("J", 10),
            Map.entry("K", 11)
        );
        System.out.println("Large Immutable Map: " + largeMap);
        
        // Attempting to modify immutable collections
        try {
            immutableList.add("Orange"); // This will throw UnsupportedOperationException
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify immutable list: " + e.getMessage());
        }
        
        try {
            immutableSet.remove("Red"); // This will throw UnsupportedOperationException
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify immutable set: " + e.getMessage());
        }
        
        try {
            immutableMap.put("Four", 4); // This will throw UnsupportedOperationException
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify immutable map: " + e.getMessage());
        }
    }
}

// Output:
// Immutable List: [Apple, Banana, Cherry]
// Immutable Set: [Red, Green, Blue]
// Immutable Map: {One=1, Two=2, Three=3}
// Large Immutable Map: {A=1, B=2, C=3, D=4, E=5, F=6, G=7, H=8, I=9, J=10, K=11}
// Cannot modify immutable list: null
// Cannot modify immutable set: null
// Cannot modify immutable map: null

Stream Changes

Java 9 introduces new Stream API methods including takeWhile, dropWhile, and iterate with predicate support. These methods enhance stream processing capabilities by providing more control over element filtering and iteration, enabling more efficient and readable stream operations.

import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;

public class StreamChangesExample {
    public static void main(String[] args) {
        /*
         * Java 9 Stream API Changes:
         * 
         * 1. takeWhile(Predicate<T> predicate)
         *    - Takes elements while predicate is true
         *    - Stops at first element that doesn't match
         * 
         * 2. dropWhile(Predicate<T> predicate)
         *    - Drops elements while predicate is true
         *    - Takes remaining elements after first mismatch
         * 
         * 3. Stream.iterate(T seed, Predicate<T> hasNext, UnaryOperator<T> next)
         *    - Creates finite stream with predicate-based termination
         *    - Alternative to infinite iterate() method
         * 
         * 4. Stream.ofNullable(T t)
         *    - Creates stream from nullable object
         *    - Returns empty stream if input is null
         *    - Returns single-element stream if input is not null
         */
        
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // takeWhile - takes elements while predicate is true
        System.out.println("=== takeWhile Example ===");
        List<Integer> takeWhileResult = numbers.stream()
            .takeWhile(n -> n < 5)
            .collect(Collectors.toList());
        System.out.println("takeWhile(n < 5): " + takeWhileResult);
        
        // dropWhile - drops elements while predicate is true, then takes the rest
        System.out.println("\n=== dropWhile Example ===");
        List<Integer> dropWhileResult = numbers.stream()
            .dropWhile(n -> n < 5)
            .collect(Collectors.toList());
        System.out.println("dropWhile(n < 5): " + dropWhileResult);
        
        // iterate with predicate - new overloaded method
        System.out.println("\n=== iterate with Predicate Example ===");
        List<Integer> iterateResult = Stream.iterate(1, n -> n < 10, n -> n + 2)
            .collect(Collectors.toList());
        System.out.println("iterate(1, n < 10, n + 2): " + iterateResult);
        
        // ofNullable - creates stream from nullable object
        System.out.println("\n=== ofNullable Examples ===");
        String nonNullString = "Hello";
        String nullString = null;
        
        List<String> nonNullResult = Stream.ofNullable(nonNullString)
            .collect(Collectors.toList());
        System.out.println("ofNullable(nonNullString): " + nonNullResult);
        
        List<String> nullResult = Stream.ofNullable(nullString)
            .collect(Collectors.toList());
        System.out.println("ofNullable(nullString): " + nullResult);
        
        // Practical example combining multiple methods
        System.out.println("\n=== Combined Example ===");
        List<String> words = List.of("apple", "banana", "cherry", "date", "elderberry", "fig");
        
        List<String> combinedResult = words.stream()
            .takeWhile(word -> word.length() <= 6)
            .dropWhile(word -> word.startsWith("a"))
            .collect(Collectors.toList());
        System.out.println("Combined takeWhile + dropWhile: " + combinedResult);
    }
}

// Output:
// === takeWhile Example ===
// takeWhile(n < 5): [1, 2, 3, 4]
// 
// === dropWhile Example ===
// dropWhile(n < 5): [5, 6, 7, 8, 9, 10]
// 
// === iterate with Predicate Example ===
// iterate(1, n < 10, n + 2): [1, 3, 5, 7, 9]
// 
// === ofNullable Examples ===
// ofNullable(nonNullString): [Hello]
// ofNullable(nullString): []
// 
// === Combined Example ===
// Combined takeWhile + dropWhile: [banana, cherry, date]

Private Methods in Interfaces

Java 9 introduces private methods in interfaces, allowing code reuse and better organization within interface implementations. Private methods help reduce code duplication by extracting common logic shared between default methods, improving maintainability and readability of interface code.

public interface DataProcessor {
    // Default method using private method
    default String processData(String data) {
        return formatData(validateData(data));
    }
    
    // Private method for common logic
    private String validateData(String data) {
        return data != null ? data.trim() : "";
    }
    
    // Private static method
    private static String formatData(String data) {
        return "Processed: " + data.toUpperCase();
    }
}

// Usage
DataProcessor processor = new DataProcessor() {};
String result = processor.processData("hello world");
System.out.println(result);

// Output:
// Processed: HELLO WORLD