Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
69 lines
5.3 KiB
Markdown
69 lines
5.3 KiB
Markdown
# 11. Recap: the Java 26 changes you skipped
|
|
|
|
Prev: [10. Other 27 changes](10-other-27-changes.md) · Next: [12. Version lanes 25 - 29](12-lanes-25-to-29.md)
|
|
|
|
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`](../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`](../recap26/src/FinalFieldMutation.java), transcript [70](output/70-final-field-mutation.txt).
|
|
|
|
| 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](output/52-api-diff-25-to-26.txt)).
|
|
[`AppletGone.java`](../recap26/src/broken/AppletGone.java) compiles on 25 with a `[removal]` warning and fails on 26 with `package java.applet does not exist` ([73](output/73-removed-in-26.txt)).
|
|
In the same transcript, [`ThreadStopGone.java`](../recap26/src/broken/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](output/71-aot-cache-matrix.txt)):
|
|
|
|
| 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](output/72-aot-startup.txt)): 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](output/75-http3-fallback.txt), from `javap`).
|
|
The sandbox has no UDP egress, so no real HTTP/3 exchange was run. What [`Http3Fallback`](../recap26/src/Http3Fallback.java) 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](02-g1-default.md).
|
|
|
|
## New public API without a JEP
|
|
|
|
Verified by javap diff ([52](output/52-api-diff-25-to-26.txt)) and asserted in [`Api26`](../recap26/src/Api26.java) ([74](output/74-api26.txt)): `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](10-other-27-changes.md) · Next: [12. Version lanes 25 - 29](12-lanes-25-to-29.md)
|