Java 26 and the LTS Gap: What Piles Up While You Stay on 21
Most production Java runs on 21, and will move next to 29. That means four releases of changes arriving in one upgrade. Java 26 shipped HTTP/3 in the standard HTTP client, faster startup, a free garbage collector improvement, and the first warnings in the plan to make final fields truly final. Here is what helps you, what will break your reflection-heavy libraries, and how to reduce the risk of an upgrade you have not started.
- #java
- #jvm
- #migration
- #backend
- #performance
If you run Java in production, you are almost certainly on 21. It is the long-term support release most of the industry settled on, and the next one is 29. Java 26 arrived on 17 March 2026 with ten changes, is being replaced by 27 this month, and gets updates for six months in total.
So the real question is not "should we upgrade to 26". For most teams the answer is clearly no. The question is: what is piling up between 21 and 29, and which parts will hurt when we take all of it in one jump?
That framing matters, because moving from one long-term release to the next is not four small upgrades. It is one big one, where the individual changes were designed, deprecated and removed across releases you skipped. The warning that would have told you about a problem appeared in a Java version you never ran.
Java 26 is a good sample of that pile. Here is what is in it, sorted by whether it helps you or threatens you.
The "final means final" plan has started warning
This is the item with real upgrade risk, and the one to read carefully.
JEP 500: Prepare to Make Final Mean Final adds warnings when code uses reflection to change a final field. Warnings only, in 26. The stated plan is a future release that blocks it completely.
Notice the shape of this. It is the same staged plan Java used before, when it locked down access to internal APIs: warn, then warn louder, then block by default with an escape flag, then remove the flag. Each stage lands in a different release. If you skip those releases, you only ever see the final state — an application that will not start.
Why this matters more than it sounds: changing a final field through reflection is not an exotic trick. It is normal practice in
- libraries that convert objects to and from JSON or other formats,
- dependency injection frameworks that set fields directly,
- database mapping libraries that fill in entity objects,
- mocking libraries used in tests,
- test helpers that reset static configuration between test cases.
You almost certainly do not do this in your own code. Your libraries almost certainly do.
The action you can take today, on 21, without changing your runtime: find out whether your stack depends on it. Run your test suite on a Java 26 build and collect the warnings. That gives you a list of libraries that need upgrading before the next long-term release. Doing this now, as a one-day check, is far cheaper than discovering it during an upgrade with a deadline — because the fix is often "wait for the library author", and you want that clock started early.
The reason Java is doing this at all is worth understanding rather than resenting. final is a promise the compiler relies on to optimise your code. If any field can be changed behind its back through reflection, the optimiser has to be cautious everywhere. Closing this gap is what makes a whole set of optimisations possible. The speed you get later is paid for by the flexibility you lose now.
HTTP/3 in the standard client
JEP 517 adds HTTP/3 support to the built-in HttpClient. Final, not a preview.
This is the change most likely to be immediately useful in normal backend work, because it removes a dependency instead of adding one. Until now, using HTTP/3 in Java meant pulling in a third-party client and everything it brings with it. Now it is part of the platform.
The reason to care is something called head-of-line blocking. HTTP/2 sends many requests over one TCP connection, and TCP guarantees data arrives in order — so one lost packet stalls every request sharing that connection until it is resent. HTTP/3 runs on QUIC over UDP, where requests are independent at the transport level. A lost packet stalls only the request it belonged to.
Where this turns into real numbers: unreliable or high-latency networks, mobile clients, calls between regions, and any client making many requests to one server at the same time. On a clean network between two services in the same region, the difference is close to nothing — do not expect a win where there was no packet loss to begin with. I covered the transport details in HTTP/3 and QUIC.
One operational warning that catches people: QUIC is UDP on port 443. Many corporate firewalls, older load balancers and default security group rules allow TCP on 443 but silently drop UDP. Clients usually fall back to HTTP/2, so the symptom is not an error — it is your carefully adopted HTTP/3 quietly never being used. Check which protocol was actually negotiated in production rather than assuming.
Faster startup
JEP 516: Ahead-of-Time Object Caching with Any GC finds objects that are expensive to create during startup and caches them in advance. The important part is with any GC — earlier work on this only supported specific garbage collectors, which limited who could use it.
Startup time was a minor annoyance when a service ran for months on fixed servers. It is a direct cost when you scale up in response to traffic, run Java in a serverless function, or watch a rolling deploy crawl because each instance needs warming up before it can take load.
A useful way to see it: this is Java competing with the "just use Go" argument on the one point where Go's advantage was built-in rather than cultural. It does not close the gap completely. It narrows it, and it adds up with the other startup work arriving across these releases.
G1 gets cheaper
JEP 522: G1 GC — Improve Throughput by Reducing Synchronization. G1 is the default garbage collector, so this is the rare change that helps you with no configuration and no code changes.
Less coordination work means less overhead on your application threads. Do not expect a headline number. Expect a few percent that adds up across a fleet. This category — improvements you get simply by upgrading — is the strongest argument for staying current.
Structured concurrency, sixth preview
JEP 525 is structured concurrency's sixth preview.
Read that number as information. It is the API most relevant to anyone using virtual threads. It lets you start several concurrent tasks and treat them as one unit, with sensible cancellation and error handling, instead of juggling a pile of futures. It is also an API that has been reshaped repeatedly across previews, which is exactly what previews are for.
My practical advice: learn it, build prototypes with it, do not put production code on it yet. Preview APIs can change in incompatible ways between releases, and an API on its sixth revision is telling you the design is still settling. If you already run virtual threads at scale — where the awkwardness is sharpest — follow it closely, because it is the missing half of the story I described in Java 21 virtual threads in production.
Alongside it, JEP 526: Lazy Constants is in its second preview. It gives you a supported way to delay creating a value while still letting the compiler treat it as a constant — a well-behaved replacement for the hand-written holder-class trick, and a natural fit with making final mean final.
The rest, briefly
JEP 530: Primitive Types in Patterns, instanceof, and switch — fourth preview. Makes pattern matching work with primitive types everywhere it already works with objects. Removes an inconsistency every Java developer has tripped over. Still a preview, so still subject to change.
JEP 524: PEM Encodings of Cryptographic Objects — second preview. A standard way to read and write PEM files. Small, and quietly welcome if you have ever hand-parsed a certificate or pulled in a whole crypto library just to do it.
JEP 529: Vector API — eleventh incubator. Eleven. It has been incubating since Java 16 and is waiting on other JVM work before it can become final.
JEP 504: Remove the Applet API — deprecated in Java 17, gone in 26. Only relevant to a very old codebase, but if you have one, this is a hard failure rather than a warning.
What to actually do this quarter
You are on 21. You are going to 29. Nobody is asking you to run 26 in production. Three things are still worth doing, in order of value:
1. Run your test suite on a recent Java version in CI, as a non-blocking job. One extra entry in your build matrix. It surfaces final-field warnings, removed-API failures and behaviour changes as a continuous signal instead of an upgrade-week surprise. This is by far the highest-value item here, and it costs an afternoon.
2. List your reflection-heavy libraries. Serialization, dependency injection, database mapping, mocking. For each one, check whether the maintainers have said anything about JEP 500. Libraries without a plan are your biggest risk, because you cannot fix them yourself.
3. Decide your HTTP/3 position. If you have mobile clients or cross-region service calls, HTTP/3 in the standard client is a genuine reason to want the next long-term release. Knowing that helps you justify the upgrade budget when you ask for it.
The takeaway
Nobody is asking you to run Java 26 in production. It is not a long-term release, it is already being replaced, and your real next move is 21 to 29. But that jump is not four small upgrades. It is one big one, where the deprecations were spread across releases you never ran — so the warnings that would have warned you arrive as failures instead.
JEP 500 is the concrete example to act on now. Changing a final field through reflection is standard practice in serialization libraries, dependency injection frameworks, database mappers and mocking tools. Not in your code, but very much in your dependencies. The plan to forbid it has started with warnings.
The useful work this quarter is not a version bump. Add one non-blocking CI job running a current Java version against your existing tests, and read what it reports. That turns invisible debt into a list, and it costs an afternoon. The genuine wins in 26 — HTTP/3 in the standard client, faster startup, a free G1 improvement — are the argument you will use later to fund the upgrade. The CI job is what makes the upgrade survivable when you get it.
/share

Kishore K Sharma
Lead Full Stack Engineer | Java · Spring Boot · Distributed Systems · AWS | Building Scalable Cloud-Native Platforms
Available for contract work and remote full-time roles. See what I take on.
/seen in production