Unboxing Java 26: What Developers Need to Know Before Upgrading

JDK 26 is shipping this month. If you’re planning an upgrade from JDK 25, there’s a solid mix of finalized features, continued previews, and a handful of removals that deserve your attention. Here’s the breakdown based on the Inside Java podcast and the official JEP list.

Finalized features

Five JEPs have graduated to final status in this release:

JEP 517 — HTTP/3 for the HTTP Client API. The java.net.http.HttpClient now supports HTTP/3 (QUIC) out of the box. HTTP/3 solves two long-standing problems with TCP-based HTTP: head-of-line blocking — where a single lost packet stalls all streams on a connection — and connection migration, where moving between networks (Wi-Fi to mobile, for example) previously required a full reconnection. With QUIC as the transport layer, streams are independent and connections survive network changes. If you’re already using the HTTP Client API introduced in Java 11, you benefit from these improvements without changing a single line of code. The client automatically negotiates HTTP/3 when the server supports it and falls back to HTTP/2 or HTTP/1.1 otherwise. The practical impact is most noticeable for mobile clients on unstable connections, high-latency network paths, and microservice-to-microservice communication where multiplexed streams reduce tail latency.

JEP 516 — Ahead-of-Time Object Caching with Any GC. AOT object caching works by persisting a snapshot of the heap from a training run into an archive that gets loaded at startup, allowing the JVM to skip the work of creating and initializing objects that are always needed. Previously, this was limited to the SerialGC, which meant it wasn’t practical for production workloads. In JDK 26 it now works with G1, ZGC, and Shenandoah — the collectors most applications actually use. This matters most for frameworks with large startup object graphs. Spring Boot applications, Micronaut services, and Quarkus projects that spend hundreds of milliseconds initializing dependency injection containers and configuration parsing can see significant startup time reductions. If startup performance matters to your deployment model — serverless functions, scale-to-zero containers, CLI tools — this is worth benchmarking with your actual application.

JEP 522 — G1 GC: Improve Throughput by Reducing Synchronization. G1 gets a throughput boost by reducing internal synchronization overhead in two key areas: card table scanning (the mechanism G1 uses to track cross-region references) and remembered set refinement (where G1 processes dirty cards to maintain its region-based view of the heap). Both of these previously required fine-grained locking that became a bottleneck under heavy allocation. Benchmarks have shown throughput gains in the range of 5–15% for high-allocation-rate, multi-threaded workloads — the kind you see in web application servers, batch processing pipelines, and data-intensive services. If G1 is your default collector (and for most applications it is), you benefit from this immediately without any tuning changes.

JEP 500 — Prepare to Make Final Mean Final. This is a longer-term initiative to enforce final semantics more strictly. The motivation is threefold: preserving the integrity of immutability guarantees that developers rely on, enabling the JVM to make stronger optimization assumptions (the JIT compiler can treat final fields as true constants and inline their values), and closing security holes where reflection could bypass access controls. Today, code can call setAccessible(true) on a Field object and mutate final fields, and sun.misc.Unsafe / VarHandle can do the same. This JEP issues warnings for such access now and will block it entirely in a future release. To prepare, audit your codebase for setAccessible calls targeting final fields, check for Unsafe.putObject or VarHandle writes to finals, and review any serialization or dependency injection frameworks that might modify final fields at runtime. This JEP doesn’t break anything yet, but the warnings are a clear signal to start migrating.

JEP 504 — Remove the Applet API. The Applet API, deprecated since Java 9, is now gone. If your codebase still references java.applet classes — even transitively through old libraries — this will be a compile-time and runtime error in JDK 26. To detect transitive dependencies, run jdeps --jdk-internals your-app.jar or jdeps -R -s your-app.jar and look for references to the java.applet package. Common older libraries that may still reference these classes include legacy PDF generators, older versions of Apache POI, certain Swing-based UI frameworks, and some BIRT reporting libraries. Check your dependency tree before upgrading.

Preview and incubator features

Several features continue to mature:

  • JEP 525 — Structured Concurrency (Sixth Preview). Structured concurrency’s core goal is to treat a group of concurrent tasks as a single unit of work with a clear lifecycle: when a parent task spawns subtasks, those subtasks are scoped to the parent, and if the parent is cancelled or one subtask fails, the others are automatically cancelled and cleaned up. This is fundamentally different from raw ExecutorService usage, where spawned tasks have no inherent relationship to the code that created them and cancellation must be managed manually. Six preview rounds signal that the API has largely stabilized — the StructuredTaskScope design is unlikely to change significantly. Worth experimenting with now, especially for request-handling code that fans out to multiple downstream services.
  • JEP 530 — Primitive Types in Patterns, instanceof, and switch (Fourth Preview). Pattern matching now extends to primitive types, closing a long-standing gap where pattern contexts only worked with reference types. This means you can switch on long values, use instanceof byte or instanceof int in pattern matching expressions, and deconstruct records containing primitive fields without boxing. Previously, using primitives in pattern contexts required boxing to wrapper types, which added overhead and made the code awkward — especially in data processing pipelines or protocol parsing code where you’re working with raw numeric types. The practical impact is cleaner, more efficient code when processing binary data, implementing codecs, or working with numerical computations that benefit from exhaustive switch expressions over primitive ranges.
  • JEP 524 — PEM Encodings of Cryptographic Objects (Second Preview). If you’ve ever loaded a PEM-encoded certificate or private key in Java, you know the pain: manually stripping the -----BEGIN / -----END markers, Base64-decoding the content, wrapping it in a PKCS8EncodedKeySpec or X509EncodedKeySpec, feeding it to a KeyFactory or CertificateFactory, and handling every checked exception along the way. The new PEM API reduces this to a few lines — parse a PEM string and get back the cryptographic object directly. This is a significant quality-of-life improvement for any code that deals with TLS configuration, mutual authentication, or certificate rotation.
  • JEP 526 — Lazy Constants (Second Preview). Enables lazy initialization of constants at the language level, replacing the manual patterns Java developers have used for years: double-checked locking (verbose and easy to get wrong), the holder class idiom (requires a dedicated inner class per lazy field), or AtomicReference with compareAndSet (correct but clunky). Lazy constants provide the same thread-safety guarantees — at-most-once initialization, safe publication to all threads — with a single declaration. This is most useful for expensive resources like database connection pools, compiled regex patterns, or service clients that may never be used in a given execution path, and for optional subsystems where you want to avoid paying initialization cost unless the feature is actually invoked.
  • JEP 529 — Vector API (Eleventh Incubator). The Vector API’s goal is to let developers express vector computations — operations on multiple data elements simultaneously — in a way that the JVM can reliably compile to SIMD instructions (SSE, AVX, NEON) on the underlying hardware. It’s still incubating after eleven rounds primarily because its final form depends on Project Valhalla’s value types, which would allow vector values to be represented without heap allocation overhead. The target workloads are numerical processing, ML inference pipelines, image and audio codec operations, and any algorithm that processes arrays of primitives in bulk. Production use is not yet recommended, but the API is stable enough for experimentation and benchmarking.

Removals and deprecations to watch

This is where upgrades can bite you. JDK 26 removes or deprecates several APIs:

  • Thread.stop() removed. If any code still calls this method, it will fail at compile time. Thread.stop() was dangerous because it caused a ThreadDeath error to be thrown asynchronously at an arbitrary point in the target thread’s execution, which could leave objects in an inconsistent state — locks released mid-update, partially written data structures, silently corrupted state. The replacement is the interrupt + cooperative cancellation pattern: call thread.interrupt() and have your task check Thread.interrupted() or handle InterruptedException at well-defined cancellation points.
  • SDP (Sockets Direct Protocol) support removed. Niche, but check if your networking stack relied on it.
  • jdk.jsobject module removed. This was the bridge between Java and JavaScript in browser contexts. With Applets gone, this follows naturally.
  • InterruptedIOException handling removed from java.io classes. I/O classes no longer throw InterruptedIOException — they catch and suppress the interrupt instead. The practical impact: if your code relied on catching InterruptedIOException to detect that a thread was interrupted during a blocking I/O operation, that catch block will no longer trigger. You need to explicitly check Thread.interrupted() after I/O calls to detect interruption, or restructure your cancellation logic to use NIO channels (which throw ClosedByInterruptException and still respect interrupts).
  • java.net.SocketPermission deprecated for removal. Part of the ongoing SecurityManager wind-down.

Other notable changes

  • JDBC 4.5 support is now included, aligning with the latest database connectivity spec. Key additions include virtual thread awareness (connection pools and drivers can better handle virtual-thread-per-request models) and improved ShardingKey support for applications using sharded databases.
  • Unicode 17.0.0 and CLDR 48.0 data updates ensure your application handles the latest locale and character data correctly.
  • Process API now implements Closeable, making it easier to use in try-with-resources blocks. This matters for long-running applications that spawn external processes — without explicit cleanup, process handles and their associated native resources (file descriptors for stdin/stdout/stderr pipes) can leak, eventually exhausting OS-level limits. With Closeable, a try-with-resources block ensures cleanup happens automatically.

Should you upgrade?

If you’re on JDK 25, the upgrade is relatively smooth. The main risks are the Applet API removal and Thread.stop() removal — both should only affect legacy codebases. The HTTP/3 and G1 throughput improvements make this a worthwhile upgrade for production workloads.

Run your test suite against JDK 26 EA builds now. Check for removed API usage with jdeprscan, and keep an eye on the structured concurrency and primitive patterns previews — they’re getting close to final.