Blog

Java 17 | What you need to know

Key Takeaways

  • Java 17 finalized sealed classes, added new pseudo-random number generator APIs, and introduced context-specific deserialization-filter factories.
  • Pattern matching for switch was a preview feature in Java 17; the Foreign Function & Memory API was an incubator API. They became permanent in Java 21 and Java 22, respectively.
  • The Applet API and Security Manager were deprecated for removal—not removed—in Java 17. RMI Activation and experimental compiler implementation code were removed.
  • Feature releases follow a six-month cadence. Oracle's two-year LTS cadence is a separate support policy, and Java 25 is its latest LTS release as of October 17, 2026.

Java 17's most important changes combine tighter domain modeling with platform and compatibility updates: sealed classes, switch patterns, new random-generator APIs, native interoperability, deserialization filtering, and stronger encapsulation of JDK internals. The distinction that matters is whether a change was final, preview, incubating, deprecated, or removed in Java 17—not its status in a newer JDK.

What Is a JEP?

A JDK Enhancement Proposal, or JEP, describes and tracks a significant JDK change. JEPs cover the language, libraries, JVM, tools, and development infrastructure; having a JEP number doesn't guarantee delivery in a particular release. The official guide to JEPs and preview features explains the different delivery stages.

Java 17 Features and Status at a Glance

ChangeStatus in Java 17Important distinction
Sealed classes, JEP 409FinalNo preview flags required.
Switch pattern matching, JEP 406PreviewFinalized in Java 21 by JEP 441.
Enhanced PRNGs, JEP 356FinalCommon interfaces and additional algorithms.
Foreign Function & Memory API, JEP 412IncubatorFinalized in Java 22 by JEP 454, with an evolved API.
Deserialization-filter factory, JEP 415FinalSelects filters using application context.
Applet API, JEP 398Deprecated for removalLater removed in Java 26 by JEP 504.
Security Manager, JEP 411Deprecated for removalPermanently disabled in Java 24 by JEP 486.
RMI Activation, JEP 407RemovedThe rest of RMI remained available.
Experimental AOT/JIT compilers, JEP 410Implementation code removedHotSpot's standard JIT compilers remained.

New Features in Java 17

Sealed Classes and Interfaces: JEP 409

Sealed types let you constrain which types directly extend a class or implement an interface. They're useful when you own a domain's alternatives—for example, a fixed set of product types—rather than an extension point anyone should implement. Sealed classes first appeared as a preview in Java 15 and became final in Java 17.

Save this complete example as SealedDemo.java:

public class SealedDemo {
    sealed interface Animal permits Cow, Pig {
        String makeNoise();
    }

    static final class Cow implements Animal {
        public String makeNoise() { return "moo"; }
    }

    static non-sealed class Pig implements Animal {
        public String makeNoise() { return "oink"; }
    }

    static sealed class Habitat permits Ocean, Land {}
    static final class Ocean extends Habitat {}
    static final class Land extends Habitat {}

    public static void main(String[] args) {
        Animal animal = new Cow();
        System.out.println(animal.makeNoise());
    }
}

A permitted class must continue the hierarchy as final, sealed, or non-sealed, explicitly or implicitly. Here, Cow closes its branch while Pig allows further subclassing. Permitted types must belong to the same named module, or the same package when using the unnamed module. See the sealed-class rules before designing a public hierarchy.

Pattern Matching for Switch: JEP 406

A type pattern such as case String s checks the type and introduces a correctly typed variable, avoiding a separate cast. A guard adds a condition after the type matches. Java 17's preview used && for guards; the final Java 21 syntax uses when.

Java 17 Preview Example

public class Demo17 {
    public static void main(String[] args) {
        System.out.println(printObject("hello"));
        System.out.println(printObject(100));
        System.out.println(printObject(3));
        System.out.println(printObject(null));
    }

    static String printObject(Object obj) {
        return switch (obj) {
            case null -> "Object is null";
            case String s -> "Object is string: " + s;
            case Integer i && i >= 100 ->
                "Object is a BIG integer: " + i;
            case Integer i -> "Object is an integer: " + i;
            default -> "Object is something else";
        };
    }
}

Use a JDK 17 compiler and runtime, enabling preview features at both stages:

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

Java 17 did support case null in this preview; the Java 17 switch documentation explicitly includes it. The guarded integer case must precede the unguarded integer case, otherwise the latter dominates it and compilation fails.

Final Java 21+ Syntax

Replace the method above with this fragment when moving to the final feature. It needs no preview flags for switch pattern matching.

// Java 21+ method fragment; place inside a class.
static String printObject(Object obj) {
    return switch (obj) {
        case null -> "Object is null";
        case String s -> "Object is string: " + s;
        case Integer i when i >= 100 ->
            "Object is a BIG integer: " + i;
        case Integer i -> "Object is an integer: " + i;
        default -> "Object is something else";
    };
}

These are arrow-style switch rules, not arrow functions. Switch expressions themselves became permanent in Java 14; see our Java 14 feature overview for that earlier change. The Java 21 switch documentation covers the final pattern rules.

Enhanced Pseudo-Random Number Generators: JEP 356

The java.util.random package gives you a common RandomGenerator interface and algorithm selection through RandomGeneratorFactory. This separates code that consumes randomness from the implementation you choose.

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

public class RandomDemo {
    public static void main(String[] args) {
        RandomGenerator xoshiro =
            RandomGeneratorFactory.of("Xoshiro256PlusPlus").create(42L);
        RandomGenerator lxm =
            RandomGeneratorFactory.of("L32X64MixRandom").create(42L);

        System.out.println(xoshiro.nextInt(10));
        System.out.println(lxm.nextInt(10));

        RandomGeneratorFactory.all()
            .map(factory -> factory.name())
            .sorted()
            .forEach(System.out::println);
    }
}

Both named algorithms are available in Java 17, and nextInt(10) produces a value from 0 through 9. The factory API also exposes algorithm properties and available implementations.

Choose an explicit algorithm and seed for repeatable tests. For parallel simulations, investigate splittable generators and give each task its own generator rather than sharing mutable generator state. For tokens, credentials, or other security-sensitive values, use java.security.SecureRandom, not these general-purpose algorithms.

Foreign Function & Memory API: JEP 412

Java 17's incubating FFM API supports interaction with native functions and memory outside the Java heap. It did not remove JNI. The finalized API arrived in Java 22 under JEP 454 in java.lang.foreign; don't copy Java 17 incubator imports into a modern implementation.

This Java 17 example demonstrates a bounded memory lifetime—not a native function call or a performance comparison:

import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;

public class NativeMemoryDemo {
    public static void main(String[] args) {
        try (ResourceScope scope = ResourceScope.newConfinedScope()) {
            MemorySegment memory =
                MemorySegment.allocateNative(200, 8, scope);
            MemoryAccess.setIntAtOffset(memory, 0, 17);
            System.out.println(MemoryAccess.getIntAtOffset(memory, 0));
        }
    }
}
javac --add-modules jdk.incubator.foreign NativeMemoryDemo.java
java --add-modules jdk.incubator.foreign NativeMemoryDemo

The allocation reserves 200 bytes with eight-byte alignment. Closing the scope releases that allocation; subsequent access through the segment fails. The module flags enable the incubator module, rather than a preview language feature. For new development, consult the final FFM API.

Context-Specific Deserialization Filters: JEP 415

Java serialization reconstructs objects from a byte stream; our Java serialization guide covers the mechanics. JEP 415 added a JVM-wide filter factory that can choose a filter using trusted application context, such as the current request handler or thread-local state. That's different from merely calling setObjectInputFilter on one stream.

The factory runs when an ObjectInputStream is constructed and when its stream-specific filter is set. Install it once, before any input streams exist, using ObjectInputFilter.Config.

This standalone FilterDemo.java selects a map allowlist from thread-local context, accepts a map, and demonstrates rejection separately:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;

public class FilterDemo {
    private static final ThreadLocal<ObjectInputFilter> CONTEXT =
        ThreadLocal.withInitial(() ->
            info -> ObjectInputFilter.Status.REJECTED);

    public static void main(String[] args) throws Exception {
        ObjectInputFilter limits = ObjectInputFilter.Config.createFilter(
            "maxdepth=10;maxrefs=100;maxbytes=4096;maxarray=100");

        ObjectInputFilter.Config.setSerialFilterFactory((current, requested) -> {
            ObjectInputFilter base = current != null ? current
                : ObjectInputFilter.merge(limits, CONTEXT.get());
            return requested == null ? base
                : ObjectInputFilter.merge(base, requested);
        });

        ObjectInputFilter mapPolicy = ObjectInputFilter.Config.createFilter(
            "java.util.HashMap;java.util.Map$Entry;"
                + "java.lang.Integer;java.lang.Number;!*");

        CONTEXT.set(mapPolicy);
        try {
            HashMap<String, Integer> value = new HashMap<>();
            value.put("count", 17);
            System.out.println(roundTrip(value));

            try {
                roundTrip(new ArrayList<String>());
                throw new AssertionError("Expected rejection");
            } catch (InvalidClassException expected) {
                System.out.println("ArrayList rejected");
            }
        } finally {
            CONTEXT.remove();
        }
    }

    private static Object roundTrip(Serializable value)
            throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
            output.writeObject(value);
        }
        try (ObjectInputStream input = new ObjectInputStream(
                new ByteArrayInputStream(bytes.toByteArray()))) {
            return input.readObject();
        }
    }
}

The factory composes resource limits, the context policy, and any requested filter; rejection from either merged filter wins. The allowlist includes supporting types needed by this map. These limits are illustrative, not production defaults, and the trusted call path—not the incoming payload—must select the policy.

Other Compatibility-Relevant Java 17 Changes

  • Strong encapsulation, JEP 403: most JDK internals are strongly encapsulated. In Java 17, --illegal-access only issues a warning; it no longer relaxes access. Upgrade reflection-heavy dependencies before reaching for targeted --add-opens or --add-exports workarounds.
  • Always-strict floating-point semantics, JEP 306: floating-point expressions consistently use strict semantics, eliminating the earlier distinction controlled by strictfp.
  • macOS/AArch64 port, JEP 391: the JDK gained native support for Apple Silicon. Check that any native libraries you load match the architecture of your JDK.

Deprecated Features in Java 17

Applet API: JEP 398

Java 17 deprecated the Applet API for removal; the API remained present in that release. The practical issue was the disappearance of browser plug-in support, not competition with JavaScript frameworks. The API was subsequently removed in Java 26 under JEP 504.

Security Manager: JEP 411

Java 17 deprecated the Security Manager and related APIs for removal. JEP 411 explains why: the Security Manager's permission model was brittle, rarely used to secure server-side code, and expensive to maintain, so this wasn't simply an applet cleanup.

Java 24 permanently disabled it under JEP 486. If your application depends on installing or enabling one, redesign that boundary—potentially using separate processes and operating-system isolation—rather than expecting an equivalent replacement API. The Security Manager migration guidance explains the consequences.

Removed Features in Java 17

RMI Activation: JEP 407

RMI Activation, which supported starting remote objects on demand, was removed along with the rmid tool. The rest of RMI was preserved. If you used activation, plan how your services will be started and supervised; ordinary RMI use alone doesn't require replacing your communication layer.

Experimental AOT and JIT Compilers: JEP 410

JEP 410 removed the experimental Graal-based AOT/JIT compiler implementation code from OpenJDK. Those optional components had already been absent from JDK 16 builds. It did not remove normal HotSpot JIT compilation; our Java JIT compiler explanation covers that execution model.

Check build scripts for dependencies on the old jaotc tool or experimental compiler options. Their removal isn't evidence that Java stopped supporting runtime optimization or that a particular replacement is automatically faster.

Java Releases and LTS Support in 2026

Java feature releases arrive every six months. Oracle's LTS releases now follow a two-year cadence starting with Java 21, rather than the previous three-year interval. Oracle announced that change on September 14, 2021, not in 2023, in its LTS cadence proposal.

ReleaseRelease timingOracle designation
Java 17September 2021LTS
Java 21September 2023LTS
Java 25September 2025Latest LTS as of October 17, 2026
Java 29September 2027, plannedNext planned LTS

These designations follow the Oracle support roadmap. Other OpenJDK distributions have their own update, licensing, and support arrangements; for example, Temurin publishes a separate support policy. You don't have to wait for an LTS release to use newly delivered features.

Java 17 Upgrade Checklist

  1. Choose a supported distribution and patched build. Evaluate update availability and licensing separately from the feature version. For a new baseline, compare Java 17 with newer supported releases using our guide to choosing a Java version.
  2. Check your entire toolchain. Verify framework, build-tool, plug-in, agent, and dependency compatibility. Run unit, integration, and representative workload tests on the target JDK.
  3. Scan compiled code. jdeprscan finds uses of deprecated Java SE APIs; jdeps can identify dependencies on JDK internals. Supply dependency classpaths where needed.
  4. Review operational assumptions. Look for reflection workarounds, Security Manager dependencies, RMI Activation, native libraries, and preview/incubator usage. Static scans don't replace runtime testing.
# Illustrative commands: replace app.jar with your application JAR.
jdeprscan --for-removal app.jar
jdeps --jdk-internals app.jar

For an existing Java 17 application, prioritize supported updates and compatibility cleanup. For a new project, don't choose Java 17 solely because it carries an LTS label: first establish why its compatibility constraints fit your application better than a newer supported baseline.

FAQ

Is Java 17 an LTS release?

Yes. Oracle designates Java 17 as an LTS release, but update availability and support duration depend on your JDK vendor and support arrangement.

Which Java 17 features require preview flags?

Pattern matching for switch requires --enable-preview at both compilation and runtime on JDK 17. Sealed classes are final in Java 17; switch pattern matching became final in Java 21 under JEP 441.

Did Java 17 remove the JIT compiler?

No. JEP 410 removed the experimental Graal-based AOT/JIT implementation code, not HotSpot's standard JIT compilers.

Was the Security Manager removed in Java 17?

No. Java 17 deprecated it for removal under JEP 411; Java 24 permanently disabled it under JEP 486. Applications that depend on it need redesign rather than a drop-in replacement.

Does Java 17's Foreign Function and Memory API replace JNI?

No. JEP 412 introduced an incubator API in Java 17 while JNI remained available. The finalized Foreign Function & Memory API arrived in Java 22 under JEP 454.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me