JDK 17 Key Features — Sealed Classes Finalized and the Third LTS

JDK 17 — The Third LTS

Java 17, released in September 2021, is the third LTS (Long-Term Support) release. In the LTS lineage of JDK 8 -> 11 -> 17, many enterprises are migrating from JDK 11 to 17, making it a key target version. It includes finalized Sealed Classes, a switch pattern matching preview, random generator improvements, and more.

JEPFeatureStatus
JEP 409Sealed ClassesFinalized
JEP 406Pattern Matching for switchPreview
JEP 356Enhanced Pseudo-Random Number GeneratorsFinal
JEP 412Foreign Function & Memory APIIncubator
JEP 391macOS/AArch64 PortFinal
JEP 403Strong Encapsulation of Internal APIsStrengthened
JEP 398Applet API Deprecation for RemovalDeprecated

Sealed Classes — Restricting Inheritance (JEP 409)

Sealed Classes explicitly declare which classes can extend them. Using the permits keyword to list allowed subclasses, the compiler blocks any other inheritance.

Think of a library lending system: “This book can only be borrowed by members, librarians, and administrators” — it’s like specifying an explicit allow list.

// Sealed interface: only permitted implementations can exist
sealed interface Shape permits Circle, Rectangle, Triangle {}

// Each implementation must declare final, sealed, or non-sealed
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
// non-sealed: allows further inheritance
non-sealed class Triangle implements Shape {
    private final double base;
    private final double height;

    Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    double base() { return base; }
    double height() { return height; }
}

public class SealedClassExample {
    // Sealed + Record combination for area calculation
    static double area(Shape shape) {
        if (shape instanceof Circle c) {
            return Math.PI * c.radius() * c.radius();
        } else if (shape instanceof Rectangle r) {
            return r.width() * r.height();
        } else if (shape instanceof Triangle t) {
            return 0.5 * t.base() * t.height();
        }
        throw new IllegalArgumentException("Unknown shape");
    }

    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rect = new Rectangle(4.0, 6.0);
        Shape tri = new Triangle(3.0, 8.0);

        System.out.printf("Circle area: %.2f%n", area(circle));     // Circle area: 78.54
        System.out.printf("Rectangle area: %.2f%n", area(rect));    // Rectangle area: 24.00
        System.out.printf("Triangle area: %.2f%n", area(tri));      // Triangle area: 12.00
    }
}

Subclasses of a Sealed Class must choose one of the following:

ModifierMeaning
finalNo further inheritance allowed
sealedRestricts subclasses again with permits
non-sealedRemoves inheritance restriction (free inheritance like regular classes)

Records are implicitly final, so they can be used without additional declaration. When switch pattern matching is finalized in JDK 21, combining it with Sealed Classes enables exhaustive switch (guaranteed complete branching).

Pattern Matching for switch — Preview (JEP 406)

JDK 17 introduced pattern matching in switch statements as a preview. It extends instanceof to switch statements, allowing clean expression of multiple type branches.

public class SwitchPatternPreview {
    // JDK 17 preview feature — requires --enable-preview at compile time
    static String format(Object obj) {
        return switch (obj) {
            case Integer i    -> "Integer: " + i;
            case Long l       -> "Long: " + l;
            case Double d     -> String.format("Double: %.2f", d);
            case String s     -> "String(length=" + s.length() + "): " + s;
            case int[] arr    -> "int array, size=" + arr.length;
            case null         -> "null value";
            default           -> "Other: " + obj.getClass().getSimpleName();
        };
    }

    public static void main(String[] args) {
        System.out.println(format(42));              // Integer: 42
        System.out.println(format(3.14));            // Double: 3.14
        System.out.println(format("Java 17"));       // String(length=7): Java 17
        System.out.println(format(new int[]{1, 2})); // int array, size=2
        System.out.println(format(null));            // null value
    }
}

Since this is a preview feature, the --enable-preview flag is required for both compilation and execution.

javac --enable-preview --release 17 SwitchPatternPreview.java
java --enable-preview SwitchPatternPreview

This feature goes through previews in JDK 18-20 and is finalized in JDK 21.

Enhanced Pseudo-Random Number Generators (JEP 356)

The existing java.util.Random class had barely changed since 1995. JDK 17 introduces the RandomGenerator interface, enabling various algorithms through a unified API.

import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
import java.util.stream.Collectors;

public class RandomGeneratorExample {
    public static void main(String[] args) {
        // Using the default RandomGenerator
        RandomGenerator rng = RandomGenerator.getDefault();
        System.out.println("Default random: " + rng.nextInt(100));

        // Selecting a specific algorithm
        RandomGenerator xoshiro = RandomGenerator.of("Xoshiro256PlusPlus");
        System.out.println("Xoshiro256++: " + xoshiro.nextInt(100));

        // List available algorithms
        String algorithms = RandomGeneratorFactory.all()
            .map(RandomGeneratorFactory::name)
            .sorted()
            .collect(Collectors.joining(", "));
        System.out.println("Algorithm list: " + algorithms);
        // Algorithm list: L128X1024MixRandom, L128X128MixRandom, ...
        //               Xoshiro256PlusPlus, ...

        // Combined with Stream API — generate 5 random numbers between 1-100
        String randomNumbers = xoshiro.ints(5, 1, 101)
            .mapToObj(String::valueOf)
            .collect(Collectors.joining(", "));
        System.out.println("5 random numbers: " + randomNumbers);
        // 5 random numbers: 72, 15, 88, 43, 61 (varies per run)
    }
}
AlgorithmCharacteristicsUse Case
L64X128MixRandomFast, sufficient qualityGeneral simulation
Xoshiro256PlusPlusVery fast, high qualityGames, scientific computing
L128X1024MixRandomVery long periodLarge-scale parallel simulation
SecureRandomCryptographic safetySecurity, token generation

Major Changes: Removals and Deprecations

JDK 17 removed or deprecated several legacy features.

Applet API Deprecation for Removal (JEP 398): java.applet.Applet was marked as forRemoval. Since all major browsers had already dropped plugin support, there is virtually no real-world impact.

Security Manager Deprecation for Removal (JEP 411): SecurityManager was marked as forRemoval. Its complex policy configuration saw very low actual usage.

RMI Activation Removal (JEP 407): The RMI Activation mechanism was completely removed. RMI itself remains.

AOT and Graal JIT Compiler Removal (JEP 410): The experimental AOT/JIT compiler built into the JDK was removed. GraalVM can be used separately.

macOS/AArch64 Port (JEP 391)

Official JDK builds that run natively on Apple Silicon (M1, M2) Macs were included. Running directly without Rosetta 2 emulation means Apple Silicon Mac users can experience significant performance improvements.

Migration Points from JDK 11 to 17

Key items to check when upgrading from JDK 11 LTS to JDK 17 LTS:

AreaChangeAction
Internal APIs--illegal-access option removedExplicitly open with --add-opens
Text BlocksJDK 13-15 preview -> finalUse multi-line strings
RecordsJDK 14-16 preview -> finalReplace DTO/VO classes
Sealed ClassesJDK 15-17 preview -> finalUse for type hierarchy restriction
instanceof patternsJDK 14-16 preview -> finalEliminate casting boilerplate
NullPointerExceptionImproved messages since JDK 14Easier debugging
GCZGC/Shenandoah production-readyConsider low-latency GC

The most common migration issue is internal API access blocking. javax.xml.bind, javax.annotation, etc. were already removed in JDK 11, so separate dependencies must be added.

<!-- Migrate javax.xml.bind -> jakarta.xml.bind -->
<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.0</version>
</dependency>

Summary

JDK 17 is a version that combines LTS stability with modern features.

  • Sealed Classes: Explicitly restrict inheritance hierarchies with permits. Combined with Records, enables modeling close to algebraic data types (ADT)
  • Switch pattern matching (preview): Replace instanceof if-else chains with switch. Finalized in JDK 21
  • RandomGenerator: Use various random algorithms through a unified interface
  • Apple Silicon native: Native performance on macOS ARM64
  • Legacy cleanup: Applet, Security Manager, RMI Activation removed or deprecated for removal

Migration from JDK 11 to 17 is mostly smooth, but internal API direct access and Java EE module removal impacts should be checked in advance. Modern frameworks like Spring Boot 3.x and Jakarta EE 10 require JDK 17 as the minimum version, so if you haven’t migrated yet, it’s highly recommended to start planning.

Was this article helpful?