Java 26 went GA on 2026/03/17.
There are a lot of preview features again, but there are some changes around HTTP/3, G1 GC, and virtual threads that could quietly make a real difference.
On the flip side, if your environment is still carrying legacy APIs or old JVM flags, there are clear spots where an upgrade could trip you up.
Especially coming from Java 8, the gap is large enough that it’s better to look at where you’ll get stuck before looking at what’s new in Java 26.
Overview Diagram

Java 8 in production
|
+-- Audit legacy APIs / libraries
| - javax.xml.bind
| - Thread.stop
| - sun.*
| - Old JVM flags
|
+-- Validate on an LTS first
| - Test on Java 17 or 21
|
+-- Then check Java 26 deltas
- HTTP/3
- G1 improvements
- Virtual thread changes
- Security default changes
What Looks Good
java.net.http.HttpClient now supports HTTP/3.
Being able to use HTTP/3 without major code changes on the app side is straightforwardly useful.
Beyond that, there are fairly solid performance improvements: reduced synchronization in G1 GC, better humongous object reclamation, and AOT Object Cache now supporting any GC.
Virtual threads have also been improved — they’re less likely to hold onto the carrier thread while waiting for class initialization, which should reduce weird blocking scenarios.
Some of the smaller but welcome additions:
Processnow implementsAutoCloseableUUID.ofEpochMillis()was added, making UUIDv7-style handling easierByteOrderis now an enum, making it easier to use in switch statements
For example, the HTTP Client lets you benefit with minimal code changes:
var client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/api/status"))
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
And since Process now implements AutoCloseable, cleanup after running external commands is a bit cleaner:
try (var process = new ProcessBuilder("java", "-version").start()) {
var exitCode = process.waitFor();
System.out.println("exit=" + exitCode);
}
Things to Watch Out For
First, Thread.stop() has been removed.
If it’s still lingering in legacy maintenance code, it won’t even compile on JDK 26.
The Applet API has also been removed, so environments pulling from old documentation or samples need to watch out.
JVM flag cleanup has also progressed — flags like -Xmaxjitcodesize, MaxRAM, and AggressiveHeap that you’ve been using out of inertia may need to be revisited.
RMI over TLS now enforces endpoint identification by default, so environments with sloppy certificate SANs may hit connection failures.
HttpRequest.Builder.timeout() now covers the full response body reception, not just the initial connection. Depending on your existing timeout design, this could cause noticeable behavioral differences.
For environments that have been running on Java 8 for a long time, here’s what to be aware of before jumping straight to Java 26:
- Java 8 ran on the classpath model, but from Java 9 onward, module boundaries and internal API dependencies become visible
- Java EE / CORBA modules were removed in Java 11, so if
javax.xml.bindis still in your codebase, you’ll need a separate fix - Reflection and security defaults have become stricter — code that used to work silently may now warn or fail
- Old TLS settings, keystores, and RMI connections are prone to breaking right after an upgrade
For Japanese business systems, character encoding is also a quiet minefield.
Banking and core business systems in particular often still assume Shift_JIS-family encodings for back-office and host system integrations.
If you naively consolidate everything to “UTF-8 is the standard now,” you can end up with insidious bugs where the UI works fine but reports or external integrations produce garbled text.
In the Java 8 era, there was a lot of code that happened to work because the default charset on Windows was windows-31j.
But since JDK 18, the default charset is UTF-8, so patterns like new String(bytes) or FileReader that rely on the implicit charset will behave differently after migration.
In practice, you also shouldn’t treat Shift_JIS and windows-31j as interchangeable.
Both are available in Java’s charset list, but windows-31j / MS932 includes Windows-specific extensions, so there can be mismatches with circled numbers, platform-dependent characters, and IBM/NEC extensions.
For banking file transfers and host connections, it’s safer to confirm upfront whether the other party expects “Shift_JIS but actually CP932,” “strictly within the Shift_JIS range,” or “IBM host code pages included.”
If you’re looking at Japanese language issues specifically, these should be part of your pre-migration checklist:
- Is the charset explicitly specified in byte array conversions?
- Are you conflating
Shift_JISandwindows-31j? - Have you verified round-trip correctness for circled numbers, wave dash, fullwidth tilde, platform-dependent characters, and gaiji?
- For report CSVs, fixed-length files, and host transmission: are you mixing up character-based and byte-based lengths?
- Can you detect unmappable characters instead of silently replacing them?
So if you’re coming from Java 8, rather than going straight to 26 in production, it’s more realistic to first get your build and tests passing on LTS 17 or 21, strip out the old dependencies there, and then evaluate Java 26.
Java 26 itself is interesting, but absorbing the delta from Java 8 is where most of the real work lies in practice.
What to Check Before Upgrading
The fastest first step is to do a rough scan for deprecated APIs and flags.
grep -R "Thread\.stop\|Xmaxjitcodesize\|AggressiveHeap\|MaxRAM" ./
If you want to catch Java 8–era dependencies as well, run this too:
grep -R "javax\.xml\.bind\|javax\.activation\|CORBA\|sun\." ./
To do a rough scan for charset assumptions, this helps catch things you might miss:
grep -R "Shift_JIS\|MS932\|windows-31j\|Cp943\|Cp930\|EBCDIC\|file.encoding" ./
On the Java side, it’s safer to explicitly specify charsets and detect unmappable characters rather than relying on the default charset:
var charset = Charset.forName("windows-31j");
var encoder = charset.newEncoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
var bytes = encoder.encode(CharBuffer.wrap("顧客コード①"));
System.out.println(bytes.remaining());
Here’s a rough comparison:
- Java 26 from Java 8’s perspective: Large gap — this is a migration project
- Java 26 from Java 17’s perspective: Mainly evaluating new features and checking default value changes
- Java 26 from Java 21’s perspective: Migration cost is relatively light
In more practical terms:
| Perspective | Java 8 | Java 17 | Java 21 | Java 26 |
|---|---|---|---|---|
| Position in the field | Still common in legacy systems | Solid first migration target | Current primary candidate | Early evaluation and tracking candidate |
| Migration difficulty | Hardest starting point | Good landing pad from Java 8 | Easy to extend from 17 | Relatively light from 21 onward |
| Key concerns | Java EE removal, internal API dependencies | Reflection and module boundaries | Virtual thread adoption decisions | HTTP/3, GC improvements, default value changes |
| Recommended approach | Start with an audit | Get CI passing first | Easiest to standardize for production | Validate deltas in test environments |
For Java 8 projects, before getting excited about Java 26’s new features, the real topic is usually figuring out how to peel off Java 8–era technical debt.
Conversely, if you’re already on Java 17 or 21, Java 26 isn’t a “full migration” — it’s more about evaluating how to incorporate performance improvements and default value changes.
Here are some things worth verifying in CI for peace of mind:
- HttpClient timeout and header behavior
- RMI / TLS communication involving certificate validation
- Runtime creation with
jlink - XML Signature and legacy keystore dependencies
- Round-trip tests for Shift_JIS / windows-31j / host integration files
Preview / incubator features are interesting, but they’re probably better viewed as evaluation targets rather than production-ready at this point.
Summary
Java 26 is less of a single blockbuster and more of a stack of solid improvements across performance, standard APIs, and operationally safer defaults.
For typical business systems, HTTP/3, GC, and virtual thread improvements are positive moves forward.
On the other hand, the more legacy code and legacy runtime flags your environment carries, the more important it is to audit first and upgrade second.
For Japanese environments, character encoding in particular shouldn’t be put off.
In shops where Shift_JIS-family encodings or host system integrations are still in play, fixing default charset dependencies and Japanese round-trip issues takes priority over evaluating Java 26’s new features.
Especially coming from Java 8, doing an intermediate cleanup on LTS 17 or 21 first and then going after Java 26’s benefits is the more sensible path.
