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
| Change | Status in Java 17 | Important distinction |
|---|---|---|
| Sealed classes, JEP 409 | Final | No preview flags required. |
| Switch pattern matching, JEP 406 | Preview | Finalized in Java 21 by JEP 441. |
| Enhanced PRNGs, JEP 356 | Final | Common interfaces and additional algorithms. |
| Foreign Function & Memory API, JEP 412 | Incubator | Finalized in Java 22 by JEP 454, with an evolved API. |
| Deserialization-filter factory, JEP 415 | Final | Selects filters using application context. |
| Applet API, JEP 398 | Deprecated for removal | Later removed in Java 26 by JEP 504. |
| Security Manager, JEP 411 | Deprecated for removal | Permanently disabled in Java 24 by JEP 486. |
| RMI Activation, JEP 407 | Removed | The rest of RMI remained available. |
| Experimental AOT/JIT compilers, JEP 410 | Implementation code removed | HotSpot'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 Demo17Java 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 NativeMemoryDemoThe 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-accessonly issues a warning; it no longer relaxes access. Upgrade reflection-heavy dependencies before reaching for targeted--add-opensor--add-exportsworkarounds. - 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.
| Release | Release timing | Oracle designation |
|---|---|---|
| Java 17 | September 2021 | LTS |
| Java 21 | September 2023 | LTS |
| Java 25 | September 2025 | Latest LTS as of October 17, 2026 |
| Java 29 | September 2027, planned | Next 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
- 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.
- 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.
- Scan compiled code. jdeprscan finds uses of deprecated Java SE APIs; jdeps can identify dependencies on JDK internals. Supply dependency classpaths where needed.
- 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.jarFor 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
- JDK 17 release notes — feature changes and compatibility details.
- Java 17 sealed classes — permitted types and hierarchy constraints.
- Java 17 preview features — compiler/runtime flags and release restrictions.
- java.util.random package documentation — algorithms and selection guidance.
- Java 17 MemorySegment API — native allocation, bounds, and scopes.
- Serialization filtering guide — filter patterns, limits, and factories.
- JDK migration and strong encapsulation — internal APIs and targeted access options.
- Oracle Java SE support roadmap — LTS designations and vendor-specific support terms.
