Skip to content
Java modern java 7 min read

Java 17 LTS Features

Java 17, released in September 2021, is the second long-term support (LTS) release after Java 11 and one of the most feature-rich Java versions in years. It finalizes several features that were in preview — records, sealed classes, pattern matching, and text blocks — making them officially production-ready.

Note: Java 17 also removes or deprecates a number of older APIs (including the Security Manager and RMI Activation). If you are migrating from Java 11, check the migration guide for removals.

Records (Finalized in Java 16, Stable in 17)

Records are immutable data classes that eliminate the constructor/getter/equals/hashCode/toString boilerplate you normally write by hand.

public record Point(int x, int y) {}

public class RecordsDemo {
    public static void main(String[] args) {
        Point p = new Point(3, 7);
        System.out.println(p.x());      // accessor
        System.out.println(p.y());
        System.out.println(p);          // toString()
        System.out.println(p.equals(new Point(3, 7))); // true
    }
}

Output:

3
7
Point[x=3, y=7]
true

The compiler generates a canonical constructor, accessors (no get prefix), and correct equals, hashCode, and toString implementations. Records are implicitly final and extend java.lang.Record.

Tip: Records work naturally with pattern matching and can implement interfaces, making them great building blocks for algebraic data modeling.


Sealed Classes

Sealed classes let you explicitly control which classes or interfaces are allowed to extend or implement a type. This makes class hierarchies exhaustive and intentional.

public sealed interface Shape
    permits Circle, Rectangle, Triangle {}

public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double base, double height) implements Shape {}

public class SealedDemo {
    static double area(Shape s) {
        return switch (s) {
            case Circle c    -> Math.PI * c.radius() * c.radius();
            case Rectangle r -> r.width() * r.height();
            case Triangle t  -> 0.5 * t.base() * t.height();
        };
    }

    public static void main(String[] args) {
        System.out.println(area(new Circle(5)));
        System.out.println(area(new Rectangle(4, 6)));
    }
}

Output:

78.53981633974483
24.0

The permits clause lists every allowed subtype. Each permitted subtype must be final, sealed, or non-sealed. Because the compiler knows the complete set of subtypes, the switch expression above is exhaustive without a default branch.

Note: Sealed classes and their permitted subtypes must live in the same package (or the same module if using JPMS). See Java 9 Modules for module details.


Pattern Matching for instanceof

Before Java 16, checking and casting an object required two lines. Pattern matching merges them:

public class PatternMatchDemo {
    static void describe(Object obj) {
        if (obj instanceof String s) {
            // s is already a String here — no explicit cast needed
            System.out.println("String of length " + s.length());
        } else if (obj instanceof Integer i && i > 0) {
            System.out.println("Positive integer: " + i);
        } else {
            System.out.println("Something else: " + obj);
        }
    }

    public static void main(String[] args) {
        describe("Hello");
        describe(42);
        describe(3.14);
    }
}

Output:

String of length 5
Positive integer: 42
Something else: 3.14

The pattern variable (s, i) is in scope only within the branch where the match succeeds. This is finalized in Java 16 and fully stable in Java 17.


Switch Expressions (Finalized in Java 14, Stable in 17)

Switch expressions turn switch from a statement into an expression that returns a value. Java 17 ships them as fully stable.

public class SwitchExprDemo {
    public static void main(String[] args) {
        int day = 3;

        String name = switch (day) {
            case 1 -> "Monday";
            case 2 -> "Tuesday";
            case 3 -> "Wednesday";
            case 4 -> "Thursday";
            case 5 -> "Friday";
            default -> "Weekend";
        };

        System.out.println(name);
    }
}

Output:

Wednesday

The -> arrow form eliminates fall-through bugs. For multi-statement branches, use yield to return a value from a block:

String result = switch (day) {
    case 1, 2, 3, 4, 5 -> {
        System.out.println("Weekday");
        yield "Weekday";
    }
    default -> "Weekend";
};

Text Blocks (Finalized in Java 15, Stable in 17)

Text blocks let you write multi-line string literals without escape sequences, making JSON, SQL, and HTML far more readable.

public class TextBlockDemo {
    public static void main(String[] args) {
        String json = """
                {
                  "name": "Alice",
                  "age": 30,
                  "city": "Toronto"
                }
                """;

        System.out.println(json);
    }
}

Output:

{
  "name": "Alice",
  "age": 30,
  "city": "Toronto"
}

The compiler strips the common leading indentation automatically (determined by the position of the closing """). Incidental whitespace disappears; intentional indentation remains.

Tip: Text blocks support the same escape sequences as regular strings (\n, \t, etc.) plus two new ones: \<line-terminator> to suppress a newline, and \s to preserve a trailing space.


instanceof Pattern Matching in switch (Preview in Java 17)

Java 17 introduces pattern matching for switch as a preview feature. It lets you match on type and destructure in one step inside a switch:

// --enable-preview required for Java 17
public class SwitchPatternPreview {
    static String format(Object obj) {
        return switch (obj) {
            case Integer i  -> "int " + i;
            case Long l     -> "long " + l;
            case Double d   -> "double " + d;
            case String s   -> "string \"" + s + "\"";
            default         -> "other: " + obj;
        };
    }

    public static void main(String[] args) {
        System.out.println(format(42));
        System.out.println(format(3.14));
        System.out.println(format("hi"));
    }
}

Output:

int 42
double 3.14
string "hi"

Warning: Preview features require --enable-preview at both compile time and runtime. They are not yet guaranteed stable. Pattern matching for switch was finalized in Java 21 — see Java 21 LTS Features.


New java.util.random API (Pseudorandom Number Generators)

Java 17 introduces a new, unified PRNG API under java.util.random. The new RandomGenerator interface abstracts over many PRNG algorithms, and RandomGeneratorFactory lets you pick one by name.

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

public class NewRandomDemo {
    public static void main(String[] args) {
        // Use the fast, splittable L64X128MixRandom
        RandomGenerator rng = RandomGeneratorFactory
            .of("L64X128MixRandom")
            .create();

        rng.ints(5, 1, 101)
           .forEach(System.out::println);
    }
}

Output:

(5 random integers between 1 and 100)

The old java.util.Random, ThreadLocalRandom, and SplittableRandom now implement RandomGenerator, so existing code still compiles.


HexFormat Utility

java.util.HexFormat (new in Java 17) handles hex encoding and decoding without third-party libraries or hand-rolled loops.

import java.util.HexFormat;

public class HexFormatDemo {
    public static void main(String[] args) {
        HexFormat hex = HexFormat.of();

        byte[] bytes = {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE};
        String encoded = hex.formatHex(bytes);
        System.out.println(encoded);          // cafebabe

        byte[] decoded = hex.parseHex("deadbeef");
        System.out.println(decoded.length);   // 4
    }
}

Output:

cafebabe
4

HexFormat is immutable and thread-safe. It supports uppercase, delimiter separators, and prefix/suffix customization via withUpperCase(), withDelimiter(), etc.


Foreign Function & Memory API (Incubator)

Java 17 incubates the Foreign Function & Memory (FFM) API (jdk.incubator.foreign), the first step toward replacing JNI for calling native code. It lets you allocate off-heap memory and call C functions directly from Java without unsafe hacks.

Note: The FFM API graduated to preview in Java 19/20 and was finalized in Java 22. In Java 17 it requires --add-modules jdk.incubator.foreign. Use it experimentally only.


Deprecations and Removals

Java 17 removes and deprecates several legacy features:

FeatureStatusReplacement
Security ManagerDeprecated for removalOS-level sandboxing / container isolation
Applet APIDeprecated for removalWeb-based alternatives
RMI ActivationRemovedAlternative RPC / gRPC
sun.misc.Unsafe memory accessPartially deprecatedFFM API (preview)
Experimental AOT/JIT (GraalVM JVMCI)No longer shipped in JDKStandalone GraalVM

Warning: The Security Manager (java.lang.SecurityManager) is deprecated for removal in Java 17. Do not build new applications that rely on it.


Under the Hood

Sealed Classes and the Type System

At the bytecode level, sealed classes use two new class-file attributes: PermittedSubclasses (lists allowed subtypes) and Sealed (marks the class as sealed). The compiler uses these at compile time for exhaustiveness checks in switch and pattern matching. At runtime the JVM enforces that only listed subtypes can extend a sealed class — any other subclass triggers IncompatibleClassChangeError.

Records and the JVM

A record compiles to a final class extending java.lang.Record. The canonical constructor, accessors, equals, hashCode, and toString are generated directly in bytecode by the compiler (not reflectively at runtime), so they are as fast as hand-written versions. The equals and hashCode implementations use invokedynamic with a special bootstrap method — the JIT can inline and optimize these calls aggressively.

Pattern Matching and instanceof

The old obj instanceof Foo emits an instanceof bytecode instruction. Pattern matching for instanceof compiles to the same instanceof check followed by a checkcast — but the compiler guarantees the checkcast is redundant (it always succeeds), so the JIT eliminates it. The pattern variable is simply the result of the cast pinned into a local variable slot.

Text Blocks at Compile Time

Text blocks are a compile-time feature only — there is no new runtime type. The Java compiler processes leading whitespace and escape sequences during compilation and stores the result as a regular String constant in the constant pool. At runtime, text blocks are identical to ordinary string literals.


  • Sealed Classes — deep dive into the sealed/permits/non-sealed model
  • Records — immutable data classes explained in full
  • Pattern Matchinginstanceof and switch pattern matching details
  • Switch Expressions — arrow syntax, yield, and exhaustiveness
  • Text Blocks — multi-line string literals and indentation rules
  • Java 11 LTS Features — the previous LTS release with HttpClient, new String methods, and more
  • Java 21 LTS Features — the next LTS with virtual threads, finalized pattern matching, and sequenced collections
Last updated June 13, 2026
Was this helpful?