Java 26, released on March 17, 2026, continues Oracle’s six-month release cadence as the first non-LTS release since Java 25. While enterprise teams on conservative upgrade cycles may stick with Java 25 (the current LTS), Java 26 brings meaningful improvements in performance, security, language expressiveness, and modern networking.
This release includes 10 JEPs: 5 finalized, 4 in preview and 1 incubator. In this article, we will explore the new features of Java 26, with a particular focus on the finalized JEPs and their impact on Java development.
Note : Download OpenJDK 26 here: https://jdk.java.net/26/
JVM / New Evolutions in Java 26
JEP 516: Ahead-of-Time Object Caching with Any GC
JEP 516 enhances the AOT cache so that it can be used with any garbage collector, including the low-latency Z Garbage Collector (ZGC), by storing cached Java objects in a neutral, GC-agnostic format rather than a GC-specific one.
Prior to JEP 516, the AOT cache stored objects in a format that was bitwise-compatible with the heap layout of Serial, Parallel, and G1 collectors, but fundamentally incompatible with ZGC. This forced developers to choose between two powerful optimizations:
- Using ZGC to minimize GC-induced tail latency (pauses under 1ms).
- Using the AOT cache to accelerate startup (e.g. Spring PetClinic starts 41% faster).
JEP 516 eliminates this trade-off by storing object references as logical indices instead of raw memory addresses, allowing the JVM to stream and materialize cached objects into memory at startup regardless of which GC is used.
Before JEP 516
# GC-specific format — raw memory address stored in reference field value: 0x4002045278
After JEP 516
# GC-agnostic format — logical index stored in reference field value: 5
The JVM applies an automatic heuristic at cache creation time to decide whether to use the streamable (GC-agnostic) format. It kicks in under three conditions during training:
- ZGC was used (
-XX:+UseZGC) - Compressed OOPs were explicitly disabled (
-XX:-CompressedOops) - Heap was larger than 32 GB
You can explicitly opt into the GC-agnostic streamable format using the new -XX:+AOTStreamableObjects flag:
# Force streamable, GC-agnostic object caching java -XX:+AOTStreamableObjects -cp app.jar com.example.App
JEP 522: G1 GC: Improve Throughput by Reducing Synchronization
G1 has been Java’s default garbage collector since Java 9, designed to balance latency and throughput. To do its job, G1 performs work concurrently with the application, which means application threads and GC threads share the CPU and must synchronize. This synchronization overhead lowers throughput.
To track object references efficiently, G1 maintains a data structure called the card table, updated every time an object reference changes. Background optimizer threads process this card table, and doing so safely requires synchronizing with application threads, leading to slower, more complicated write-barrier code.
JEP 522 introduces a second card table to eliminate this synchronization bottleneck. Application threads write to one table without any synchronization, while optimizer threads work on the other. G1 atomically swaps the two tables as needed.
The result is 5–15% throughput gains in applications that heavily modify object-reference fields, plus up to 5% additional gains on x64 architectures from simpler write-barrier code .
Core Libs
JEP 517: HTTP/3 for the HTTP Client API
JEP 517 updates the java.net.http.HttpClient — the built-in HTTP client added in JDK 11 — to support HTTP/3, the successor to HTTP/2 standardized by the IETF in 2022.
HTTP/2 was a significant improvement over HTTP/1.1, but it still runs over TCP. This means a single lost packet blocks all streams until it’s retransmitted — the dreaded head-of-line blocking.
HTTP/3 solves these by building on QUIC (Quick UDP Internet Connections), a UDP-based transport protocol. With QUIC, each stream is independent so a lost packet only affects that stream.
Before JEP 517 — HTTP/3 required a third-party dependency
// Required external library e.g. Netty, Jetty, or Quiche4j
QuicChannel quicChannel = QuicChannel.newBootstrap(channel)
.handler(new Http3ClientConnectionHandler())
.remoteAddress(new InetSocketAddress("example.com", 443))
.connect()
.get();
After JEP 517 — HTTP/3 is a single opt-in line on the standard HttpClient
// Opt in at the client level — prefers HTTP/3, races HTTP/2 as fallback
var client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
// Or opt in at the request level — tries HTTP/3, falls back if unavailable
var request = HttpRequest.newBuilder(URI.create("https://example.com/api"))
.version(HttpClient.Version.HTTP_3)
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
The two opt-in scopes have different fallback strategies. Setting HTTP_3 on the HttpRequest sends the first request using HTTP/3 and falls back to HTTP/2 or HTTP/1.1 if an HTTP/3 connection cannot be established in time. Setting HTTP_3 on the HttpClient instead races an HTTP/3 connection and an HTTP/2 or HTTP/1.1 connection in parallel, using whichever succeeds first.
The default protocol version remains HTTP/2, so all existing code continues to work without changes.
JEP 500: Prepare to Make Final Mean Final
JEP 500 prepares the Java ecosystem for a future release that, by default, disallows the mutation of final fields through deep reflection, making Java programs safer and potentially faster.
Final fields have played a crucial role in the Java Memory Model since JDK 5 — their immutability underpins the safe initialization of objects in multi-threaded code. Unfortunately, their immutability also conflicts with the operation of serialization libraries that mutate fields to initialize objects during deserialization. This use case was sufficiently important to justify changing the reflection API in JDK 5 so that it could be used to mutate final fields.
Prior to JEP 500, the following code compiled, ran, and mutated a final field silently with no warning whatsoever:
// Before JEP 500 — silent final field mutation, no warning
class Person {
final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
var person = new Person("Alice");
Field field = Person.class.getDeclaredField("name");
field.setAccessible(true);
field.set(person, "Bob"); // mutates a final field - no warning at all
System.out.println(person.getName()); // prints: Bob
After JEP 500, the same code triggers a runtime warning by default:
WARNING: Final field name in class Person has been mutated reflectively
by class Application in unnamed module.
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning.
WARNING: Mutating final fields will be blocked in a future release.
The behavior is controlled by a new --illegal-final-field-mutation option, similar in spirit to --illegal-access introduced in JDK 9 and --illegal-native-access in JDK 24. It works as follows:
--illegal-final-field-mutation=allowallows the mutation to proceed without warning.--illegal-final-field-mutation=warnallows the mutation but issues a warning the first time that code in a particular module performs an illegal final field mutation. At most one warning per module is issued. This mode is the default in JDK 26. It will be phased out in a future release and, eventually, removed.--illegal-final-field-mutation=debugis identical towarnexcept both a warning message and a stack trace are issued for every illegal final field mutation.--illegal-final-field-mutation=denywill result inField::setthrowing anIllegalAccessExceptionfor every illegal final field mutation. This mode will become the default in a future release.
JDK 26 also integrates this with JDK Flight Recorder via a new jdk.FinalFieldMutation event, which records every mutation attempt with a full stack trace — useful for auditing entire dependency trees before the hard cutoff arrives.
For example, here is how to create a JFR recording and then display the jdk.FinalFieldMutation events:
$ java -XX:StartFlightRecording:filename=recording.jfr ... $ jfr print --events jdk.FinalFieldMutation recording.jfr
JEP 500 is part of the broader Integrity by Default initiative, which has been closing unsafe API loopholes one JEP at a time: strongly encapsulating JDK internals (JEP 396/403), restricting dynamic agent loading (JEP 451), deprecating sun.misc.Unsafe memory access (JEP 471/498), restricting JNI usage (JEP 472), and now targeting final field mutation.
JEP 504: Remove the Applet API
JEP 504 removes the entire java.applet package from JDK 26, making it the first Java version to ship without the Applet API — 10 years after its deprecation process began.
The java.applet.Applet class shipped with JDK 1.0 in 1996. At the time, the majority of Java use cases revolved around applets — small programs embedded in web browsers that added interactive capabilities impossible with early HTML. The lifecycle of these programs was managed entirely by the java.applet API.
For code that used Applet as a UI container, the AWT API provides alternatives. For code that used AudioClip for audio playback, the javax.sound.SoundClip class introduced in JDK 25 is the recommended replacement. For standalone application deployment, jpackage creates native platform installers.
Preview / Incubator JEPs in Java 26
While this article focuses on the finalized JEPs of Java 26 — the features you can safely use in production today — the release also includes several preview and incubator JEPs. These represent features still in evolution: previews are nearly complete but may undergo adjustments, and incubators expose APIs for early feedback. Here’s a quick look at what’s on the horizon.
JEP 524: PEM Encodings of Cryptographic Objects (Second Preview)
Introduces an API for encoding and decoding cryptographic objects — public keys, private keys, certificates, and certificate revocation lists — to and from the widely-used PEM (Privacy-Enhanced Mail) format, the de facto standard outside of the Java world. The two core classes are PEMEncoder and PEMDecoder, with a DEREncodable marker interface retrofitted onto existing types like X509Certificate.
This is its second preview, with several changes from Java 25: the PEMRecord class is renamed to PEM and gains a new decode() method; the encryptKey methods on EncryptedPrivateKeyInfo are renamed to encrypt and now accept DEREncodable objects rather than just PrivateKey, enabling encryption of KeyPair and PKCS8EncodedKeySpec objects as well; and EncryptedPrivateKeyInfo gains new getKeyPair methods for decrypting PKCS#8-encoded text that contains a PublicKey.
JEP 525: Structured Concurrency (Sixth Preview)
JEP 525 simplifies concurrent programming by treating groups of related tasks running in different threads as single units of work, streamlining error handling, cancellation, and observability. The API centers on StructuredTaskScope, which confines subtask lifetimes to a clear lexical scope, ensuring they succeed, fail, and are cancelled together — eliminating the thread leaks and orphaned tasks common with ExecutorService/Future patterns.
This is its sixth preview, building on the significant restructuring of JDK 25 which replaced public constructors with static factory methods. JEP 525 focuses on small incremental refinements: a new onTimeout() callback on Joiner letting custom joiners react to timeouts and return partial or fallback results instead of always throwing; Joiner.allSuccessfulOrThrow() now returns a List<T> directly instead of a stream of Subtask handles; anySuccessfulResultOrThrow() has been renamed to anySuccessfulOrThrow() for brevity; and the StructuredTaskScope.open() overload now expects a UnaryOperator<Configuration> instead of a Function for tighter typing.
JEP 526: Lazy Constants (Second Preview)
JEP 526 introduces java.lang.LazyConstant<T> — an immutable value holder that is initialized at most once, guaranteed thread-safe, combining the safety of final fields with the flexibility of deferred initialization. It addresses a long-standing tension in Java: final fields enforce immutability but require eager initialization at construction time, while mutable fields allow lazy init but sacrifice thread-safety guarantees and JVM constant-folding optimizations.
// Replaces double-checked locking, volatile fields, and holder class idioms private static final LazyConstant<ConnectionPool> POOL = LazyConstant.of(() -> new ConnectionPool(MAX_CONNECTIONS)); // Access — initializes on first call, cached forever after pool.get().acquire(); // Lazy collections — each slot initializes independently on first access static final List<OrderController> ORDERS = List.ofLazy(10, _ -> new OrderController());
JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)
Extends pattern matching, instanceof, and switch so that primitive types (e.g. int, long, float, double) can be used in all pattern contexts, not just reference types. This eliminates friction when integrating primitives with modern pattern-matching features and makes the language more uniform.
// Primitive types in switch patterns (preview)
Object value = getSomeValue();
switch (value) {
case int i when i > 0 -> System.out.println("Positive int: " + i);
case long l -> System.out.println("Long: " + l);
case double d -> System.out.println("Double: " + d);
default -> System.out.println("Other: " + value);
}
This is its fourth preview — previously previewed in Java 23, 24, and 25.
JEP 529: Vector API (Eleventh Incubator)
The Vector API provides an explicit way to express SIMD-style vector computations that map to optimal CPU vector instructions at runtime. This is its eleventh incubation — it remains in incubator status pending the completion of Project Valhalla, which will provide the value types the API needs to be finalized efficiently.
Conclusion
Java 26 reaffirms the platform’s steady six-month evolution rhythm. The finalized JEPs deliver concrete, production-ready improvements: faster startup now compatible with ZGC, better G1 throughput out of the box, HTTP/3 networking support, and long-overdue API cleanup. The preview and incubator features — lazy constants, structured concurrency, primitive patterns, and PEM encodings — hint at the continued maturation of Project Loom, Project Amber, and eventually Project Valhalla.
As always, adopting the stable features today while keeping an eye on the emerging ones will ensure you get the best of both worlds — reliability and innovation.
Is Java 26 a Long-Term Support (LTS) version?
No, Java 26 is a standard feature release with 6 months of support. The current LTS version remains Java 25. Java 26 is ideal for testing the latest innovations before they are stabilized in a future LTS release.
Why is final field mutation now triggering a warning?
This is part of the “Integrity by Default” initiative. Allowing the modification of immutable fields via reflection compromises compiler optimizations and data safety. Eventually, this practice will be completely blocked (deny mode).
Can I use HTTP/3 with any server?
The HttpClient in Java 26 supports HTTP/3, but the remote server must also support this protocol (based on QUIC/UDP). If the server does not support it, the Java client will automatically fall back to HTTP/2 or HTTP/1.1 depending on your configuration.
What is the concrete benefit of AOT Object Caching for ZGC?
Prior to Java 26, ZGC users could not benefit from object caching at startup. Now, they can combine ZGC’s ultra-low latency (pauses < 1ms) with significantly faster application startup (up to 41% on certain frameworks).
What are the main differences between Java 25 and Java 26?
While Java 25 focused on stability as an LTS release with 18 JEPs, Java 26 introduces 10 JEPs centered on modern networking (HTTP/3) and JVM performance tuning. Java 26 also tightens security by warning against final field mutation, a practice that was still silent in Java 25.
Should I upgrade from Java 25 to Java 26?
If you prioritize long-term stability and support, stay on Java 25 (LTS). However, if your application requires HTTP/3 support or if you use ZGC and want faster startup times, upgrading to Java 26 will provide immediate technical advantages.