Feature Freeze for JDK 26: What Will the New Edition Bring? - JVM Weekly vol. 157
Starting December 4th (yeah, missed it due to yester-week announcement), JDK 26 entered the Rampdown phase!
Rampdown phase means that the feature list has been frozen and no further new features are to be expected.
Unlike JDK 25, which came with a massive 24 JEPs (including a significant LTS release milestone), JDK 26 is a much smaller release with only 10 JEPs. However, don’t let the smaller number fool you - this release brings some genuinely interesting changes, including the long-awaited HTTP/3 support and significant GC improvements.
The production release is scheduled for March 17, 2026, following a second rampdown phase starting January 15, with two release candidates expected in February.
Now, let’s go through the complete list of changes in the new edition.
Exegi monumentum - Stable Feature
JEP 517: HTTP/3 for the HTTP Client API
JEP 517 finally brings HTTP/3 support to the Java Platform (at least natively). The HTTP Client API (introduced in JDK 11) is updated to support the HTTP/3 protocol, which uses QUIC transport instead of TCP.
Why HTTP/3 matters: HTTP/3 offers potentially faster handshakes, avoidance of head-of-line blocking, and more reliable transport especially in environments with high packet loss. It’s already supported by most browsers and deployed on about a third of all websites.
Usage: HTTP/3 is opt-in. You can enable it per-client or per-request:
var client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();Or you can enable HTTP/3 for a specific request:
var request = HttpRequest.newBuilder(URI.create(”https://example.com/”))
.version(HttpClient.Version.HTTP_3)
.GET().build();The implementation transparently downgrades to HTTP/2 or HTTP/1.1 if the target server doesn’t support HTTP/3. Various discovery modes are available to control this behavior via the H3_DISCOVERY request option.
Goodbye, Old Friends - Removals and Preparations
JEP 504: Remove the Applet API
Probably the second biggest announcement this week. JEP 504 finally removes the Applet API from the Java Platform. This has been a long time coming – the API was deprecated in JDK 9 (2017), deprecated for removal in JDK 17 (2021), and now, with the Security Manager permanently disabled in JDK 24, there’s simply no reason to keep this obsolete code around.
What gets removed: The entire java.applet package (Applet, AppletContext, AppletStub, AudioClip), java.beans.AppletInitializer, javax.swing.JApplet, and any API elements that reference these classes.
Web browsers haven’t supported applets for years, and there hasn’t been a way to run applets using the JDK since the appletviewer tool was removed in JDK 11. It’s time to say goodbye to this relic of Java’s early web days.
Does anybody miss the old guy?
JEP 500: Prepare to Make Final Mean Final
JEP 500 begins the process of enforcing the integrity of final fields. Currently, the deep reflection API (setAccessible and set methods of java.lang.reflect.Field) allows final fields to be mutated at will – making the final keyword essentially meaningless from a runtime perspective.
The problem: Final fields represent immutable state, crucial for reasoning about correctness and enabling JVM optimizations like constant folding. But the ability to mutate them via reflection undermines all of this.
The solution: In JDK 26, mutating final fields via deep reflection will issue warnings by default. A future release will throw exceptions instead. Developers can opt in to final field mutation using --enable-final-field-mutation=MODULE_NAME when necessary.
Serialization libraries should migrate to using sun.reflect.ReflectionFactory, which provides a supported way to deserialize objects with final fields without requiring the --enable-final-field-mutation flag.
Performance - VM Internals
JEP 516: Ahead-of-Time Object Caching with Any GC
JEP 516 enhances the AOT cache (introduced in JEP 483) so that it can be used with any garbage collector, including ZGC. This is a significant improvement for Project Leyden’s startup optimization efforts.
The problem: Previously, cached Java objects were stored in a GC-specific format, making the AOT cache incompatible with ZGC. This forced users to choose between low-latency garbage collection OR fast startup - but not both.
The solution: Objects can now be cached in a GC-agnostic format using logical indices instead of memory addresses. When the cache is loaded, a background thread materializes objects one by one, converting indices to actual addresses appropriate for the current GC.
The JDK now includes two baseline AOT caches – one GC-agnostic and one GC-specific – and automatically selects the appropriate one based on the environment.
JEP 522: G1 GC: Improve Throughput by Reducing Synchronization
This time, a bit more theory needed.
G1 needs to know which parts of the Java heap were modified by the application so it can perform garbage collection safely. To do this, the heap is divided into small chunks called cards, and G1 maintains a card table—a simple structure that records which cards have been changed. Every time your code updates an object reference, the corresponding card is marked.
Before JEP 522, both application threads and garbage-collection threads updated the same card table. Because many threads were touching this shared structure, G1 had to use synchronization. Under heavy load, this locking became a bottleneck and reduced overall throughput.
JEP 522 changes this by introducing a second card table. Application threads now update one card table without any locking, using much simpler and faster write-barrier code. At the same time, GC threads work on the other card table. When G1 needs to hand work from the application to the GC, it simply swaps the two tables atomically.
This matters because updating object references is one of the most frequent operations in Java programs. Making this path cheaper improves performance across the board, especially for highly concurrent applications.
In practice, this results in a 5–15% throughput improvement for applications that heavily modify object references, and up to 5% improvement even for others. The write-barrier code was reduced from around 50 CPU instructions to about 12 on x64 systems. The extra memory cost is small - about 2 MB per 1 GB of heap - and is largely offset by memory optimizations introduced in recent JDK releases.
Nihil Novi Sub Sole - Preview Features Continue
JEP 526: Lazy Constants (Second Preview)
The goal of LazyConstant is to compute a value exactly once, in a thread-safe way, but only when it’s first accessed, not at class-initialization time. At the same time, it is designed so that once the value is initialized, the JVM can treat it like a constant and apply constant-folding and inlining optimizations, avoiding repeated reads or synchronization overhead.
In short: LazyConstant combines lazy initialization, safe concurrency, and JIT-level performance close to real constants. JEP 526 re-previews the API for deferred immutability, now renamed from StableValue to LazyConstant. The API has been significantly simplified to focus on high-level use cases.
Key changes from JDK 25:
Renamed from StableValue to LazyConstant
Removed low-level methods (orElseSet, setOrThrow, trySet)
Factory methods for lazy lists and maps moved to List.ofLazy() and Map.ofLazy()
null is no longer allowed as a computed value
private final LazyConstant<Logger> logger
= LazyConstant.of(() -> Logger.create(OrderController.class));
void submitOrder() {
logger.get().info(”order started”); // Initialized on first access
}JEP 525: Structured Concurrency (Sixth Preview)
Structured Concurrency API treats groups of related tasks running in different threads as single units of work, streamlining error handling and cancellation. JEP 525 continues the preview of the Structured Concurrency API with minor refinements.
Changes in this new preview:
New Joiner.onTimeout() method for handling timeout expiration
allSuccessfulOrThrow() now returns List<T> instead of Stream<Subtask<T>>
anySuccessfulResultOrThrow() renamed to anySuccessfulOrThrow()
Static open method now takes UnaryOperator instead of Function
Response handle() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());
scope.join();
return new Response(user.get(), order.get());
}
}JEP 524: PEM Encodings of Cryptographic Objects (Second Preview)
PEM encoding/decoding API provides a concise way to convert between PEM text and cryptographic objects like keys, certificates, and certificate revocation list. JEP 524 continues the preview of it with several refinements.
Changes:
PEMRecord is now named PEM and includes a decode() method
EncryptedPrivateKeyInfo gets new encrypt() and getKeyPair() methods
PEMEncoder and PEMDecoder now support KeyPair and PKCS8EncodedKeySpec encryption/decryption
String pem = PEMEncoder.of().encodeToString(privateKey);PrivateKey key = PEMDecoder.of().decode(pem, PrivateKey.class);JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)
The feature enables uniform (finally!) data usage with type patterns for all types, whether primitive or reference, and allows switch to process values of any primitive type including boolean, float, double, and long. JEP 530 previews for the fourth time the extension of pattern matching to primitive types... so you probably know the drill 😉
Changes in this preview: Enhanced definition of unconditional exactness and tighter dominance checks in switch constructs. These changes enable the compiler to identify a wider range of coding errors, although some previously legal switch constructs will now be rejected.
// Primitive type patterns in switch
switch (x.getYearlyFlights()) {
case 0 -> “No flights”;
case 1 -> “One flight”;
case int i when i >= 100 -> “Gold status!”;
case int i -> “Regular: “ + i + “ flights”;
}
// instanceof with primitives - safe conversion check
int i = 1000;
if (i instanceof byte b) {
// Only enters if i fits in a byte
}JEP 529: Vector API (Eleventh Incubator)
The API legend (or rather myth) allowing to express vector computations that compile to optimal SIMD instructions on supported CPUs. JEP 529 continues the incubation of the Vector API without substantial changes. As a reminder - the Vector API will remain in incubation until necessary features from Project Valhalla (value classes) become available as preview features. At that point, it will be adapted to use them and promoted to preview status.
And with that, a small comment at the end... I honestly expected to see more Valhalla here by JDK 26, but we still don’t have it.
So it looks like we’ll probably have to wait a bit longer for the Vector API as well.
This is the last edition before Christmas - next Thursday we have Christmas Day, do you believe? 🎄
I wish you a calm end of the year, a few days without alerts, incidents, and “quick questions”, and at least one moment to properly unplug - whether “unplug” means to you.
Merry Christmas and happy holidays to everyone - see you next year ❤️
BTW: if you’re looking for interesting technical articles for the holiday season, I highly recommend Java Advent Calendar!
It’s an initiative running since 2012, where every day for 24 days of December (the Advent period) a new technical article appears from various authors in the JVM community. Similar idea to Advent of Code, but instead of algorithmic puzzles – a solid dose of knowledge.
This year Olimpiu Pop did great job... like every year.










