Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
5.3 KiB
11. Recap: the Java 26 changes you skipped
Prev: 10. Other 27 changes · Next: 12. Version lanes 25 - 29
Java 26 (GA 2026-03-17) is also a non-LTS release. If you are on 25 and going to 27 you meet all of it at once. Script: scripts/recap26.sh.
JEP 500: final means final
Reflection has always let Field.setAccessible(true) followed by Field.setInt(...) overwrite a final field. Source: FinalFieldMutation, transcript 70.
| Run | Result |
|---|---|
| JDK 25 | silent |
| JDK 26 default | three WARNING: lines, then it proceeds |
JDK 26 --enable-final-field-mutation=ALL-UNNAMED |
silent (the opt-in the warning names) |
JDK 26 --illegal-final-field-mutation=deny |
IllegalAccessException: "... is not allowed to mutate final fields" |
JDK 26 --illegal-final-field-mutation=debug |
the warning plus the stack trace of the offending call |
--illegal-final-field-mutation takes allow, warn, debug or deny. A JFR event, jdk.FinalFieldMutation, is recorded with default settings whenever a mutation is permitted
(one event, with the declaring class and field name); under deny there is nothing to record.
Even when it works it misleads: the field reads 9090 but port() still returns 8080, because final int port = 8080 is a constant variable and javac copied the literal into the method.
Who this hits: serialisation libraries, mocking frameworks, dependency-injection containers and test code that set final fields. Run your test suite once with deny on 26 or later.
JEP 504: the Applet API is gone
java.applet.*, javax.swing.JApplet and the AppletInitializer overload of java.beans.Beans.instantiate were removed (52).
AppletGone.java compiles on 25 with a [removal] warning and fails on 26 with package java.applet does not exist (73).
In the same transcript, ThreadStopGone.java: Thread.stop() (which has thrown UnsupportedOperationException since 20) no longer exists at all: cannot find symbol.
JEP 516: AOT object caching with any GC
The AOT cache (JEP 483 / 514 / 515 in 24 and 25) stores pre-parsed and pre-linked classes and, in some configurations, pre-built heap objects such as the module graph.
Until 26 the heap-object part needed G1. Workflow used here: -XX:AOTMode=record -XX:AOTConfiguration=..., then -XX:AOTMode=create -XX:AOTCache=..., then run with -XX:AOTCache=....
What the JVM's own log says (71):
| Cache built under | Run under | JDK 25 | JDK 26 / 27 |
|---|---|---|---|
| G1 | G1 or Serial | heap objects used | heap objects used |
| G1 | ZGC | cache refused | cache refused |
| ZGC | ZGC | cache used, heap objects not used | heap objects used |
| ZGC | G1 or Serial | cache refused | cache refused |
So "any GC" means each GC can now benefit, not that one cache serves every GC: the cache records whether compressed oops were on
(The saved state of UseCompressedOops (1) is different from runtime (0), CDS will be disabled), and ZGC does not use them. Build the cache under the collector you deploy with.
Startup, median of 9 runs, indicative only (72): with ZGC, JDK 25 barely moves (about 83 -> 81 ms, because no heap objects are used), while JDK 26 and 27 go from about 62 to about 40 ms; with G1 all three go from about 60 to about 35-37 ms.
A 27 JVM has a new diagnostic flag AOTCompatibleOopCompression; setting it at cache creation did not make a G1-built cache load under ZGC in one attempt, and nothing more was investigated.
JEP 517: HTTP/3 in the HTTP client
HttpClient.Version.HTTP_3, HttpOption.H3_DISCOVERY and HttpOption.Http3DiscoveryMode (ANY, ALT_SVC, HTTP_3_URI_ONLY) are the API surface (75, from javap).
The sandbox has no UDP egress, so no real HTTP/3 exchange was run. What Http3Fallback shows instead: a client that prefers HTTP/3 against a TLS server that only speaks HTTP/1.1
falls back and response.version() says HTTP_1_1; a request pinned to HTTP_3_URI_ONLY fails with HttpConnectTimeoutException: quic handshake timeout.
JEP 522: G1 throughput
"Improve throughput by reducing synchronization between application and GC threads." Not benchmarked here; it is a claim from the JEP, and it is the reason 26 is a fairer G1 baseline than 25 in chapter 2.
New public API without a JEP
Verified by javap diff (52) and asserted in Api26 (74): String.equalsFoldCase, compareToFoldCase and UNICODE_CASEFOLD_ORDER
("STRASSE".equalsFoldCase("straße") is true; equalsIgnoreCase says false), UUID.ofEpochMillis (a version-7 UUID for a given instant), Process implementing Closeable,
MemoryMXBean.getTotalGcCpuTime(), ByteOrder becoming an enum, HPKEParameterSpec (Hybrid Public Key Encryption), and the Unicode 17 blocks in Character.UnicodeBlock.
Prev: 10. Other 27 changes · Next: 12. Version lanes 25 - 29