Java 27 and 26: runnable demos and captured output for every JEP, plus version lanes

This commit is contained in:
2026-09-21 13:55:46 +00:00
commit eecdf0e444
96 changed files with 3606 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
build/
*.class
*.jfr
.lib/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Ankur Mhatre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+68
View File
@@ -0,0 +1,68 @@
# javademos
Runnable companion code for the ankurm.com post **"Java 27 Is Out: Every JEP, Plus the Java 26 Changes You Skipped"**, and for the Java version guides
(<https://ankurm.com/java-21-to-25-lts-features/>). Every number and transcript in the post comes from a file in `docs/output/`, produced by a script in `scripts/`.
## What was tested against
| Thing | Version | Note |
|---|---|---|
| JDK 25 (LTS) | Temurin 25.0.4.1+1 | the baseline for "broke on 26" |
| JDK 26 | Amazon Corretto 26.0.2.1 | GA 2026-03-17, non-LTS |
| JDK 27 | Amazon Corretto 27+35 | GA 2026-09-15, non-LTS |
| Containers | `amazoncorretto:26`, `amazoncorretto:27` | same vendor for both, so only the JDK version changes |
| JOL | 0.17 | downloaded and sha1-checked by `scripts/object-headers.sh` |
Next LTS: Java 29, September 2027 (Oracle Java SE Support Roadmap). Layout: `g1-container/`, `object-headers/`, `jep-tour/` (27 features and `broken/` 26-era sources),
`recap26/` (the Java 26 lane), `other-changes/`, `api-diff/`.
## Quickstart
```bash
export JDK25=/path/to/jdk-25 JDK26=/path/to/jdk-26 JDK27=/path/to/jdk-27 # defaults are in scripts/env.sh
./scripts/jep-tour.sh # the five 27 feature demos, self-asserting
./scripts/recap26.sh # the 26 recap lane
./scripts/run-all.sh # regenerate every docs/output/*.txt (about 15-20 minutes; needs Docker)
```
Timings, RSS, GC counts and the Vector species are machine dependent; treat them as shape, not results. Everything else is byte-stable.
## Chapters
| # | Chapter |
|---|---|
| 1 | [What shipped, and what to believe](docs/01-what-shipped.md) |
| 2 | [G1 becomes the default, even on one CPU (JEP 523)](docs/02-g1-default.md) |
| 3 | [Compact object headers (JEP 534)](docs/03-compact-headers.md) |
| 4 | [Post-quantum TLS (JEP 527)](docs/04-post-quantum-tls.md) |
| 5 | [Primitive patterns (JEP 532)](docs/05-primitive-patterns.md) |
| 6 | [Lazy constants (JEP 531)](docs/06-lazy-constants.md) |
| 7 | [Structured concurrency (JEP 533)](docs/07-structured-concurrency.md) |
| 8 | [PEM encodings (JEP 538)](docs/08-pem-api.md) |
| 9 | [JFR redaction (JEP 536) and the Vector API (JEP 537)](docs/09-jfr-redaction-and-vector.md) |
| 10 | [Other 27 changes](docs/10-other-27-changes.md) |
| 11 | [Recap: the Java 26 changes you skipped](docs/11-recap-26.md) |
| 12 | [Version lanes: 25 to 29](docs/12-lanes-25-to-29.md) |
| 13 | [Upgrade checklist](docs/13-upgrade-checklist.md) |
## Captured output (`docs/output/`)
| File | Produced by | What it is |
|---|---|---|
| `01`-`03` | `g1-default.sh` | which collector each JDK picks, by container size; the opt-out |
| `04`-`06` | `g1-cost.sh`, `g1-tuning.sh` | what G1 costs on one CPU, and two tested explanations |
| `10`-`15` | `object-headers.sh` | JOL instance sizes on 26, 27, 27 opted out; the JOL record failure and workaround |
| `20-broken-*` | `broken-on-27.sh` | javac's own messages for 26-era preview code on 27 |
| `21`-`25` | `jep-tour.sh` | the five 27 feature demos |
| `30`-`32` | `tls-pq.sh` | key-exchange matrix, ClientHello size, the opt-out |
| `40` | `jfr-redaction.sh` | five redaction configurations |
| `50`-`52` | `api-diff.sh` | public API diffs, 26 to 27, 25 to 26, Vector only |
| `60`-`62` | `other-changes.sh` | removed options, behaviour changes, new 27 API |
| `70`-`75` | `recap26.sh` | final-field mutation, AOT cache matrix and startup, removals, 26 API, HTTP/3 |
## Not reproduced
`jdk.java.net/27` and `openjdk.org/jeps` returned 403 while this was written; the JEP list was cross-checked against four secondary sources and the JDK binaries themselves.
HTTP/3 was not spoken to a real HTTP/3 server (no UDP egress). JEP 522 was not benchmarked. JDK-8377013 could not be reproduced.
MIT licensed, see `LICENSE`.
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""Diff two javap dumps produced by dump_api.py and report changes to PUBLIC API only.
usage: apidiff.py <old-dump> <new-dump> [--vector-only]
A class is reported only if its declaration starts with 'public' in the old or the new dump, so
package-private implementation classes (which javap -public still lists) do not drown the signal.
"""
import collections
import re
import sys
def parse(path):
classes = collections.OrderedDict()
decl = {}
cur = None
for line in open(path):
line = line.rstrip("\n")
if line.startswith("#####") or line.startswith("Compiled from") or not line.strip():
continue
if not line.startswith(" "):
m = re.search(r"(?:class|interface|enum|@interface|record)\s+([\w.$]+)", line)
cur = m.group(1) if m else line
classes[cur] = set()
decl[cur] = line
elif cur is not None and line.strip() != "}":
classes[cur].add(line.strip())
return classes, decl
def is_public(decl_line):
return decl_line.startswith("public ") or decl_line.startswith("protected ")
def main():
old, old_decl = parse(sys.argv[1])
new, new_decl = parse(sys.argv[2])
vector_only = "--vector-only" in sys.argv
def keep(name):
if vector_only and not name.startswith("jdk.incubator.vector."):
return False
if name.startswith("file") or "Error: Access Flags" in old_decl.get(name, "") + new_decl.get(name, ""):
return False
return is_public(old_decl.get(name, "")) or is_public(new_decl.get(name, ""))
print("## public classes removed")
for c in sorted(set(old) - set(new)):
if keep(c):
print(" " + c)
print("## public classes added")
for c in sorted(set(new) - set(old)):
if keep(c):
print(" " + c)
print("## public class changes")
for c in sorted(set(old) & set(new)):
if not keep(c):
continue
removed = old[c] - new[c]
added = new[c] - old[c]
d_old, d_new = old_decl[c], new_decl[c]
if removed or added or d_old != d_new:
print(c)
if d_old != d_new:
print(" - " + d_old.strip())
print(" + " + d_new.strip())
for x in sorted(removed):
print(" - " + x)
for x in sorted(added):
print(" + " + x)
main()
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Dump the public API signatures of every exported class in the JDK's java.se-ish modules.
usage: dump_api.py <JAVA_HOME> <out-file>"""
import subprocess, sys, re, os
home, out = sys.argv[1], sys.argv[2]
env = {k:v for k,v in os.environ.items() if k!='JAVA_TOOL_OPTIONS'}
jimage = subprocess.run([f'{home}/bin/jimage','list',f'{home}/lib/modules'],capture_output=True,text=True,env=env).stdout
mods = {}
cur=None
for line in jimage.splitlines():
if line.startswith('Module: '): cur=line.split(': ')[1].strip(); mods[cur]=[]
elif cur and line.strip().endswith('.class'):
mods[cur].append(line.strip()[:-6])
WANT = re.compile(r'^(java|jdk\.(jfr|httpserver|incubator\.vector|management|net|jshell|jartool|jlink|jpackage|javadoc|compiler|jcmd|unsupported|security\.auth)|javafx)')
res=[]
for m,classes in sorted(mods.items()):
if not WANT.match(m): continue
names=[]
for c in classes:
if c=='module-info': continue
pk=c.rsplit('/',1)[0].replace('/','.') if '/' in c else ''
if re.search(r'(^|\.)(internal|impl)(\.|$)|^sun\.|^com\.sun\.(?!net\.httpserver|management|security\.auth|source)',pk): continue
names.append(c.replace('/','.'))
if not names: continue
# javap handles binary names with $ for nested
names=[n for n in names if not re.search(r'\$\d',n)]
txt=''
for i in range(0,len(names),200):
p=subprocess.run([f'{home}/bin/javap','--module',m,'-public']+names[i:i+200],capture_output=True,text=True,env=env)
txt+=p.stdout
res.append(f'##### MODULE {m}\n'+txt)
open(out,'w').write('\n'.join(res))
print(out, sum(len(r) for r in res)//1024,'KB')
+52
View File
@@ -0,0 +1,52 @@
# 1. What shipped, and what to believe
Next: [2. G1 becomes the default](02-g1-default.md)
Java 27 reached General Availability on 2026-09-15. It is **not** an LTS: Oracle's support roadmap lists Premier support for 27 until March 2027,
and names Java 29 (September 2027) as the next LTS after 25. Everything in this repository was run on real JDK binaries; where a claim comes from
prose rather than from a run, the chapter says so.
## The nine JEPs in 27
| JEP | Title | Status in 27 | Chapter |
|---|---|---|---|
| 523 | Make G1 the Default Garbage Collector in All Environments | final | [2](02-g1-default.md) |
| 534 | Compact Object Headers by Default | final | [3](03-compact-headers.md) |
| 527 | Post-Quantum Hybrid Key Exchange for TLS 1.3 | final | [4](04-post-quantum-tls.md) |
| 532 | Primitive Types in Patterns, instanceof, and switch | fifth preview | [5](05-primitive-patterns.md) |
| 531 | Lazy Constants | third preview | [6](06-lazy-constants.md) |
| 533 | Structured Concurrency | seventh preview | [7](07-structured-concurrency.md) |
| 538 | PEM Encodings of Cryptographic Objects | third preview | [8](08-pem-api.md) |
| 536 | JFR: In-Process Data Redaction | final | [9](09-jfr-redaction-and-vector.md) |
| 537 | Vector API | twelfth incubator | [9](09-jfr-redaction-and-vector.md) |
## The ten JEPs in 26 (the release many teams skipped)
| JEP | Title | Status in 26 | Chapter |
|---|---|---|---|
| 500 | Prepare to Make Final Mean Final | final | [11](11-recap-26.md) |
| 504 | Remove the Applet API | final | [11](11-recap-26.md) |
| 516 | Ahead-of-Time Object Caching with Any GC | final | [11](11-recap-26.md) |
| 517 | HTTP/3 for the HTTP Client API | final | [11](11-recap-26.md) |
| 522 | G1 GC: Improve Throughput by Reducing Synchronization | final | [11](11-recap-26.md) (not measured here) |
| 524 | PEM Encodings of Cryptographic Objects | second preview | [8](08-pem-api.md) |
| 525 | Structured Concurrency | sixth preview | [7](07-structured-concurrency.md) |
| 526 | Lazy Constants | second preview | [6](06-lazy-constants.md) |
| 529 | Vector API | eleventh incubator | [9](09-jfr-redaction-and-vector.md) |
| 530 | Primitive Types in Patterns, instanceof, and switch | fourth preview | [5](05-primitive-patterns.md) |
## How the list was verified
openjdk.org/jeps and jdk.java.net/27 returned HTTP 403 from the sandbox used to write this, and no workaround was attempted. The lists were
cross-checked between the Inside.java release announcement, Oracle's release blog, InfoWorld's and foojay's write-ups, and then against the
**binaries**: the JDK 27 and 26 builds used here were diffed class by class ([51](output/51-api-diff-26-to-27.txt),
[52](output/52-api-diff-25-to-26.txt)), which is how two API names in a secondary write-up were corrected. Treat the JEP titles above as
"as reported by four secondary sources"; treat every behaviour as "observed on Corretto 26.0.2.1 / 27+35".
## What was not reproduced
* JDK-8377013 (a reported `TimeZone.getDefault()` regression) could not be reproduced and is left out.
* HTTP/3 was never spoken to a real HTTP/3 server: the sandbox has no UDP egress ([11](11-recap-26.md)).
* JEP 522 (G1 synchronization) was not benchmarked.
Next: [2. G1 becomes the default](02-g1-default.md)
+56
View File
@@ -0,0 +1,56 @@
# 2. G1 becomes the default, even on one CPU (JEP 523)
Prev: [1. What shipped](01-what-shipped.md) &middot; Next: [3. Compact object headers](03-compact-headers.md)
## The old rule
Since JDK 9 the JVM has chosen its collector by "server-class machine" ergonomics: G1 if it sees at least **two** CPUs and roughly **1792 MB**
of memory, Serial otherwise. A container limited to one CPU, or to 1 GB, quietly got Serial. JEP 523 removes the exception: G1 is chosen
in every environment.
## What the containers say
[`scripts/g1-default.sh`](../scripts/g1-default.sh) runs [`GcReport`](../g1-container/src/GcReport.java) in `amazoncorretto:26` and `:27`
containers. Only the JDK version and the limits change.
| Container | JDK 26 picks | JDK 27 picks | Transcript |
|---|---|---|---|
| `--cpus=1 --memory=2g` | Serial (`UseSerialGC ... ERGONOMIC`) | G1 | [01](output/01-g1-default-1cpu-vs-2cpu.txt) |
| `--cpus=2 --memory=2g` | G1 | G1 | [01](output/01-g1-default-1cpu-vs-2cpu.txt) |
| `--cpus=2 --memory=1g` | Serial | G1 | [02](output/02-g1-default-memory.txt) |
So both halves of the old rule are gone: CPU count *and* memory limit. An explicit `-XX:+UseSerialGC` still wins
([03](output/03-g1-default-optout.txt)); its origin shows as `VM_CREATION`, not `ERGONOMIC`.
## What it costs when your container is small
[`Workload`](../g1-container/src/Workload.java) allocates 50,000 short-lived objects per batch, keeps about 1% of them, and reports wall time, GC
count and time, the slowest single batch and peak RSS. Three runs per configuration, 1 CPU, 2 GB ([04](output/04-g1-cost-1cpu.txt)):
| 4000 batches | wall time | GC count / total GC time | slowest batch | peak RSS |
|---|---|---|---|---|
| 26, default (Serial) | 6.0-6.4 s | 1275 / ~1.85 s | 115-142 ms | 557 MB |
| 27, default (G1) | 8.2-8.4 s | 140 / ~1.98 s | 35-41 ms | 547 MB |
| 27, `-XX:+UseSerialGC` | 5.0-5.7 s | 1264 / ~1.66 s | 119-128 ms | 557 MB |
The shape, not the digits: on one CPU, G1 has roughly **3x shorter worst-case stalls** and roughly **30-35% lower throughput** on this allocation-heavy loop.
G1's concurrent work has to share the only CPU with your application.
A short-lived process makes it look worse ([05](output/05-g1-cost-1cpu-short.txt), 400 batches): 26 finishes in ~0.53 s using ~102 MB RSS; 27 with G1 takes
~1.0 s and touches ~492 MB, because G1 grows the heap toward the 512 MB default maximum instead of collecting early. Serial on 27 matches 26 (~0.52 s, ~98 MB).
## Two suspects, tested
[`scripts/g1-tuning.sh`](../scripts/g1-tuning.sh) ([06](output/06-g1-tuning-1cpu.txt)):
1. JDK 27 also changed the heap free ratios G1 uses (`MinHeapFreeRatio` 40 -> 0, `MaxHeapFreeRatio` 70 -> 100). Restoring 40/70 changed nothing measurable.
2. G1 being allowed to grow toward `MaxHeapSize`. Capping the heap at `-Xmx256m` cut peak RSS from ~492 MB to ~294 MB with about the same wall time.
## Advice
* Nothing to do if you already set a collector explicitly.
* If you were relying on the implicit Serial in small containers and you are throughput- or footprint-bound, pin it: `-XX:+UseSerialGC`.
* If you are happy with G1, **set `-Xmx`** (or `-XX:MaxRAMPercentage`) so the heap does not sprawl to the default maximum.
* Note `-XX:InitiatingHeapOccupancyPercent` is deprecated in 27 in favour of `-XX:G1IHOP` ([60](output/60-removed-options.txt)).
Prev: [1. What shipped](01-what-shipped.md) &middot; Next: [3. Compact object headers](03-compact-headers.md)
+36
View File
@@ -0,0 +1,36 @@
# 3. Compact object headers are the default (JEP 534)
Prev: [2. G1](02-g1-default.md) &middot; Next: [4. Post-quantum TLS](04-post-quantum-tls.md)
Every Java object starts with a header. On JDK 26 with default settings it is **12 bytes** (8 bytes mark word plus a 4-byte compressed class pointer).
With compact object headers the two are squeezed into **8 bytes**. JEP 534 makes that the default in 27; `-XX:-UseCompactObjectHeaders` turns it off.
## Measured with JOL
[`scripts/object-headers.sh`](../scripts/object-headers.sh) downloads JOL 0.17 (sha1-checked) and runs [`HeaderDemo`](../object-headers/src/HeaderDemo.java) three ways.
The summary is [15](output/15-headers-summary.txt); raw runs are [10](output/10-headers-jdk26.txt) (26), [11](output/11-headers-jdk27.txt) (27),
[12](output/12-headers-jdk27-opt-out.txt) (27 with the opt-out, which reproduces 26 exactly).
| Object | JDK 26 | JDK 27 |
|---|---|---|
| `Object` | 16 B | 8 B |
| `Point { int x; int y; }` | 24 B | 16 B |
| `Order { long id; int quantity; boolean paid; String customer; }` | 32 B | 32 B |
| boxed `Long` | 24 B | 16 B |
| boxed `Integer` | 16 B | 16 B |
| `int[3]` | 32 B | 24 B |
| 1,000,000 boxed `Long` in an `ArrayList` | 28.9 MB | 20.9 MB |
| 1,000,000 `Point` in an `ArrayList` | 28.9 MB | 20.9 MB |
| 1,000,000 boxed `Integer` in an `ArrayList` | 20.9 MB | 20.9 MB |
The pattern to notice: saving 4 bytes only helps when it crosses an 8-byte alignment boundary. `Order` and boxed `Integer` do not move, because padding
was already absorbing the difference. Your heap will not shrink by "4 bytes times object count"; it shrinks by the number of objects that were sitting just past a boundary.
## Two things that bite
* **JOL 0.17 cannot inspect a `record`.** `Unsafe.objectFieldOffset` refuses records ([13](output/13-jol-record-failure.txt)). The workaround
`-Djol.magicFieldOffset=true` prints a layout but shows the header mark as `N/A` ([14](output/14-jol-record-workaround.txt)).
* **Anything that hard-codes 12 bytes** (off-heap size estimators, "object overhead" constants in capacity plans, tests that assert `Instance size`) is now wrong by default.
`-XX:+UseCompressedClassPointers` is also no longer an option in 27: the JVM prints "Ignoring option UseCompressedClassPointers; support was removed in 27.0" ([60](output/60-removed-options.txt)).
Prev: [2. G1](02-g1-default.md) &middot; Next: [4. Post-quantum TLS](04-post-quantum-tls.md)
+36
View File
@@ -0,0 +1,36 @@
# 4. Post-quantum hybrid key exchange in TLS 1.3 (JEP 527)
Prev: [3. Compact headers](03-compact-headers.md) &middot; Next: [5. Primitive patterns](05-primitive-patterns.md)
JDK 27's default TLS 1.3 client offers the hybrid group **X25519MLKEM768** first: an ordinary X25519 exchange and an ML-KEM-768 key encapsulation combined, so a
recorded session stays safe even if a large quantum computer later breaks X25519.
## Who negotiates what
[`scripts/tls-pq.sh`](../scripts/tls-pq.sh) starts a loopback TLS server with [`TlsPeer`](../jep-tour/src/TlsPeer.java) on one JDK and connects with a client on
the other, for all four pairings, with `-Djavax.net.debug=ssl:handshake` ([30](output/30-tls-groups-matrix.txt)).
| Client | Server | Group selected |
|---|---|---|
| 26 | 26 | x25519 |
| 27 | 27 | **X25519MLKEM768** |
| 27 | 26 | x25519 (the 26 server does not know the hybrid group) |
| 26 | 27 | x25519 (the 26 client never offered it) |
Nothing fails when the peers disagree: the hybrid only happens when both ends are on 27 (or another stack that supports it).
## The catch: a bigger first packet
[`ClientHelloSize`](../jep-tour/src/ClientHelloSize.java) captures the first TLS record ([31](output/31-tls-clienthello-size.txt)):
| Client | ClientHello record | Fits one TCP segment (1460-byte MSS)? |
|---|---|---|
| JDK 26 | 417 bytes | yes |
| JDK 27 | 1573 bytes | **no** |
| JDK 27, `-Djdk.tls.namedGroups=x25519,secp256r1` | 408 bytes | yes |
The ML-KEM public key alone is 1184 bytes, so the ClientHello no longer fits in one segment. Middleboxes that mishandle a ClientHello spanning segments are the
known failure mode of large post-quantum ClientHellos; **that failure was not observed here** (loopback has no middleboxes). If you meet it, the opt-out is the system
property above, and the client then behaves like 26 ([32](output/32-tls-opt-out.txt)).
Prev: [3. Compact headers](03-compact-headers.md) &middot; Next: [5. Primitive patterns](05-primitive-patterns.md)
+32
View File
@@ -0,0 +1,32 @@
# 5. Primitive types in patterns (JEP 532, fifth preview)
Prev: [4. Post-quantum TLS](04-post-quantum-tls.md) &middot; Next: [6. Lazy constants](06-lazy-constants.md)
Compile and run with `--enable-preview --release 27`. Source: [`PrimitivePatterns`](../jep-tour/src/PrimitivePatterns.java), transcript [23](output/23-primitive-patterns.txt).
## The idea in one sentence
A pattern may now name a primitive type, and the test is **"can this value be converted to that type without losing information?"**. It is an exactness test, not a cast.
```java
int small = 100, big = 300;
small instanceof byte // true : 100 fits
big instanceof byte // false : a byte holds -128..127
16_777_216 instanceof float // true
16_777_217 instanceof float // false : a float has a 24-bit significand
```
You can also `switch` on a `long`, `float`, `double` or `boolean`, use `case int` against an `Integer`, and put a narrowing primitive in a record pattern:
`Reading(byte c)` matches `Reading[celsius=36]` but not `Reading[celsius=4000]` ([23](output/23-primitive-patterns.txt)).
## The compile error you will meet
Dominance rules apply. Put `case Integer i` before `case int primitive` and the second label can never match:
```
error: this case label is dominated by a preceding case label
```
The real text is in [20-broken-dominated.txt](output/20-broken-dominated.txt); source in [`Dominated.java`](../jep-tour/src/broken/Dominated.java).
Prev: [4. Post-quantum TLS](04-post-quantum-tls.md) &middot; Next: [6. Lazy constants](06-lazy-constants.md)
+23
View File
@@ -0,0 +1,23 @@
# 6. Lazy constants (JEP 531, third preview)
Prev: [5. Primitive patterns](05-primitive-patterns.md) &middot; Next: [7. Structured concurrency](07-structured-concurrency.md)
Compile and run with `--enable-preview --release 27`. Source: [`LazyDemo`](../jep-tour/src/LazyDemo.java), transcript [22](output/22-lazy.txt).
A **lazy constant** holds a value that is computed the first time somebody asks for it, exactly once, even when many threads ask at the same instant,
after which the JVM may treat it like a `final` field. It replaces the hand-written holder-class idiom and the double-checked-locking field.
```java
static final LazyConstant<Settings> SETTINGS = LazyConstant.of(LazyDemo::loadSettings);
Settings s = SETTINGS.get(); // computes on first call, returns the same instance afterwards
```
What the transcript shows, each backed by an assertion: the supplier ran once; 64 virtual threads racing on `get()` caused one initialisation; `List.ofLazy(5, i -> ...)`
computed only element 3 after `get(3)`; `Map.ofLazy(keys, f)` computed only the requested key; `Set.ofLazy(Set.of(2..7), isPrime)` answers `contains` on demand.
## What changed since 26 (the compile errors)
* `LazyConstant.orElse(...)` and `isInitialized()` are **gone** in 27 ([20-broken-lazy26.txt](output/20-broken-lazy26.txt); the class is `LazyConstant` in both, the old `StableValue` was already removed in 26 - [52](output/52-api-diff-25-to-26.txt)).
* `Set.ofLazy(Set, Predicate)` is new in 27 ([62](output/62-new-api.txt), [51](output/51-api-diff-26-to-27.txt)).
Prev: [5. Primitive patterns](05-primitive-patterns.md) &middot; Next: [7. Structured concurrency](07-structured-concurrency.md)
+27
View File
@@ -0,0 +1,27 @@
# 7. Structured concurrency (JEP 533, seventh preview)
Prev: [6. Lazy constants](06-lazy-constants.md) &middot; Next: [8. PEM](08-pem-api.md)
Compile and run with `--enable-preview --release 27`. Source: [`StructuredDemo`](../jep-tour/src/StructuredDemo.java), transcript [21](output/21-structured.txt).
The 27 API, as exercised:
```java
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
scope.fork(StructuredDemo::fetchPrice);
scope.fork(StructuredDemo::fetchStock);
List<String> results = scope.join(); // [price=42, stock=7], in fork order
}
```
* A failing subtask makes `join()` throw `ExecutionException` with the original as its cause.
* **New in 27:** `StructuredTaskScope` and `Joiner` gained a third type parameter, the exception `join()` throws. `Joiner.<String, OrderFailed>allSuccessfulOrThrow(OrderFailed::new)` makes `join()` throw *your* type.
* A timeout is a scope option (`cfg -> cfg.withTimeout(Duration.ofMillis(100))`) and surfaces as `CancelledByTimeoutException`.
## What broke from 26
[`StructuredScope26.java`](../jep-tour/src/broken/StructuredScope26.java) compiles on 26 and fails on 27 with three errors ([20-broken-structuredscope26.txt](output/20-broken-structuredscope26.txt)):
`wrong number of type arguments; required 3`, and `FailedException` / `TimeoutException` no longer exist as nested classes
([51](output/51-api-diff-26-to-27.txt): removed `StructuredTaskScope$FailedException`, `$TimeoutException`; added `$CancelledByTimeoutException`).
Prev: [6. Lazy constants](06-lazy-constants.md) &middot; Next: [8. PEM](08-pem-api.md)
+27
View File
@@ -0,0 +1,27 @@
# 8. PEM encodings (JEP 538, third preview)
Prev: [7. Structured concurrency](07-structured-concurrency.md) &middot; Next: [9. JFR redaction and the Vector API](09-jfr-redaction-and-vector.md)
Compile and run with `--enable-preview --release 27`. Source: [`PemDemo`](../jep-tour/src/PemDemo.java), transcript [24](output/24-pem.txt).
PEM is the `-----BEGIN ...-----` text format that every key and certificate file on a Linux box uses. The preview API replaces hand-rolled Base64 and header strings:
```java
String pem = PEMEncoder.of().encodeToString(keyPair.getPublic()); // -----BEGIN PUBLIC KEY-----
PublicKey back = PEMDecoder.of().decode(pem, PublicKey.class);
String enc = PEMEncoder.of().withEncryption(password).encodeToString(privateKey); // ENCRYPTED PRIVATE KEY
```
Each round trip is asserted in the demo, including: without the password `decode` returns a still-encrypted `EncryptedPrivateKeyInfo`; with `withDecryption(password)` you get the original key back.
## The trap: `new PEM(type, byte[])` does not Base64-encode
The bytes you pass are treated as the **already Base64-encoded** content and written between the header and footer verbatim
([24](output/24-pem.txt): the demo prints `hello, pem` in clear between `BEGIN ANKURM DEMO` and `END ANKURM DEMO`). To get a valid PEM, Base64-encode first and pass that; then `content()` is the Base64 text and `decode()` gives your payload back. This cost a failed assertion while writing this repository.
## What moved between 26 and 27
[`Pem26Style.java`](../jep-tour/src/broken/Pem26Style.java) compiles on 26 and produces three errors on 27 ([20-broken-pem26style.txt](output/20-broken-pem26style.txt)): `DEREncodable` became `BinaryEncodable`;
`PEMDecoder.withFactory(Provider)` is gone; `PEM.content()` now returns `byte[]` rather than `String`.
Prev: [7. Structured concurrency](07-structured-concurrency.md) &middot; Next: [9. JFR redaction and the Vector API](09-jfr-redaction-and-vector.md)
+28
View File
@@ -0,0 +1,28 @@
# 9. JFR redaction (JEP 536) and the Vector API (JEP 537)
Prev: [8. PEM](08-pem-api.md) &middot; Next: [10. Other 27 changes](10-other-27-changes.md)
## JFR redacts secrets in the recording by default
A JFR file records the JVM's command-line arguments, system properties and environment variables. Those routinely hold passwords and tokens, and recordings get attached to tickets.
[`scripts/jfr-redaction.sh`](../scripts/jfr-redaction.sh) starts an [`Idle`](../jep-tour/src/Idle.java) process with `-Dapi.token=abc123 -Dregion=ap-south-1 ... --password=hunter2 --user=ankur`
and `DB_PASSWORD=hunter2` in the environment, then prints what reached the file ([40](output/40-jfr-redaction.txt)):
| Configuration | `-Dapi.token` | `--password` | `DB_PASSWORD` | `region` |
|---|---|---|---|---|
| JDK 26 | abc123 | hunter2 | hunter2 | ap-south-1 |
| JDK 27 default | `[REDACTED]` | `[REDACTED]` | `[REDACTED]` | ap-south-1 |
| 27, `redact-argument=--user*,redact-key=region` | abc123 (exposed) | hunter2 (exposed) | hunter2 (exposed) | `[REDACTED]` |
| 27, `redact-argument=+--user*,redact-key=+region` | `[REDACTED]` | `[REDACTED]` | `[REDACTED]` | `[REDACTED]` |
| 27, `redact-argument=none,redact-key=none` | abc123 | hunter2 | hunter2 | ap-south-1 |
**The trap in row 3:** a list you supply *replaces* the built-in patterns. Without the `+` prefix you have silently turned the defaults off for everything you did not list.
## The Vector API is still incubating
[`VectorDemo`](../jep-tour/src/VectorDemo.java) ([25](output/25-vector.txt)) computes a dot product with and without `jdk.incubator.vector` and asserts they agree within 0.1%.
The same class file runs on 26 (eleventh incubator, JEP 529) and 27 (twelfth, JEP 537): the only public API change between them is that `VectorOperators` changed from `abstract` to `final`
([50](output/50-vector-api-surface.txt)). The preferred species is machine dependent (`S_512_BIT`, 16 float lanes, on the machine that wrote the transcript).
For the API itself see the ankurm.com guide: <https://ankurm.com/java-vector-api-jep-537-simd-guide/>.
Prev: [8. PEM](08-pem-api.md) &middot; Next: [10. Other 27 changes](10-other-27-changes.md)
+25
View File
@@ -0,0 +1,25 @@
# 10. The changes that are not JEPs but will still break a Monday
Prev: [9. JFR and Vector](09-jfr-redaction-and-vector.md) &middot; Next: [11. Recap of Java 26](11-recap-26.md)
Script: [`scripts/other-changes.sh`](../scripts/other-changes.sh). All from real launches; each option runs `java <option> -version` on 26 and on 27 ([60](output/60-removed-options.txt)).
| Option / property | JDK 26 | JDK 27 |
|---|---|---|
| `-noverify`, `-Xverify:none` | deprecation warning | **`Unrecognized option`, the JVM does not start** |
| `-noclassgc` | deprecation warning | **`Unrecognized option`, the JVM does not start** |
| `-XX:+UseCompressedClassPointers` | deprecated in 25 | ignored: "support was removed in 27.0" |
| `-XX:InitiatingHeapOccupancyPercent=40` | accepted | deprecated; use `-XX:G1IHOP` |
| `-XX:+UseGraalJIT` (experimental) | JVMCI error | `Unrecognized VM option` (JVMCI hook removed) |
| `-Djdk.lang.Process.launchMechanism=VFORK` | deprecation banner | "has been removed. Switching to FORK instead" ([61](output/61-behaviour-changes.txt)) |
| `-Djava.locale.useOldISOCodes=true` | works, deprecated | ignored: `Locale("iw").language` is `he` ([61](output/61-behaviour-changes.txt)) |
**The point of the table:** rows 1-2 are the ones that stop a service from starting. They are old flags that live in Dockerfiles and `JAVA_TOOL_OPTIONS` long after anyone remembers why.
Grep your images before you upgrade.
## New public API worth knowing
Found by diffing `javap` dumps ([51](output/51-api-diff-26-to-27.txt)) and exercised once each in [`Api27`](../other-changes/src/Api27.java) ([62](output/62-new-api.txt)):
`Math.acosh/asinh/atanh`, `String.encodedLength(Charset)` (length in bytes without allocating the array), `BigDecimal.rootn`, `KeyStore.getCreationInstant`.
Prev: [9. JFR and Vector](09-jfr-redaction-and-vector.md) &middot; Next: [11. Recap of Java 26](11-recap-26.md)
+68
View File
@@ -0,0 +1,68 @@
# 11. Recap: the Java 26 changes you skipped
Prev: [10. Other 27 changes](10-other-27-changes.md) &middot; 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) &middot; Next: [12. Version lanes 25 - 29](12-lanes-25-to-29.md)
+31
View File
@@ -0,0 +1,31 @@
# 12. Version lanes: 25 (LTS) to 29 (next LTS)
Ordinals in the 25 column come from Java 25's published JEP list (470, 502, 505, 507, 508), not from a run here; the existence of `StableValue` and `PEMRecord` in 25 and their absence in 26 *was* observed ([52](output/52-api-diff-25-to-26.txt)).
Prev: [11. Recap of Java 26](11-recap-26.md) &middot; Next: [13. Upgrade checklist](13-upgrade-checklist.md)
Dates and support windows are from Oracle's Java SE Support Roadmap, read on 2026-09-21: <https://www.oracle.com/java/technologies/java-se-support-roadmap.html>.
Feature notes link to the chapter that has the run.
| Release | GA | Kind | Premier support until | What to read here |
|---|---|---|---|---|
| 25 | Sept 2025 | **LTS** | Sept 2030 | the baseline for every "broke on 26/27" demo in this repository |
| 26 | March 2026 | non-LTS | Sept 2026 | [chapter 11](11-recap-26.md): final-field warnings, Applet removal, HTTP/3, AOT with any GC |
| 27 | Sept 2026 | non-LTS | March 2027 | chapters [2](02-g1-default.md) - [10](10-other-27-changes.md): G1 default, compact headers, PQ TLS, JFR redaction |
| 28 | March 2027 | non-LTS (planned) | Sept 2027 | not covered: not released |
| 29 | Sept 2027 | **LTS (planned)** | Sept 2032 | not covered: not released |
Oracle's stated cadence: "Oracle intends to make future LTS releases every two years meaning the next planned LTS release is Java 29 in September 2027."
Other vendors set their own support lengths for non-LTS builds; check the vendor you deploy.
The preview-feature ladder across the lane (each preview must be recompiled and can break the next release, see [20-broken-*](output/)):
| Feature | 25 | 26 | 27 |
|---|---|---|---|
| Structured Concurrency | 5th preview | 6th preview (JEP 525) | 7th preview (JEP 533), API reshaped |
| Lazy Constants (was Stable Values in 25) | Stable Values, preview | 2nd preview (JEP 526) | 3rd preview (JEP 531), `orElse`/`isInitialized` removed |
| PEM encodings | 1st preview (`PEMRecord`) | 2nd preview (JEP 524) | 3rd preview (JEP 538), `DEREncodable` renamed |
| Primitive patterns | 3rd preview | 4th preview (JEP 530) | 5th preview (JEP 532) |
| Vector API | 10th incubator | 11th (JEP 529) | 12th (JEP 537) |
Prev: [11. Recap of Java 26](11-recap-26.md) &middot; Next: [13. Upgrade checklist](13-upgrade-checklist.md)
+18
View File
@@ -0,0 +1,18 @@
# 13. Upgrade checklist: 25 to 27 (or to 26)
Prev: [12. Version lanes](12-lanes-25-to-29.md) &middot; [Back to the README](../README.md)
Ordered by "will stop a service from starting" first.
1. **Grep for flags that no longer exist.** `-noverify`, `-Xverify:none` and `-noclassgc` make a 27 JVM refuse to start ([60](output/60-removed-options.txt)). Look in Dockerfiles, `JAVA_TOOL_OPTIONS`, systemd units and start scripts.
2. **Pin your collector in small containers.** If nobody sets one, 27 gives you G1 on one CPU, where 26 gave you Serial ([02](02-g1-default.md)). Decide on purpose, and set `-Xmx` or `-XX:MaxRAMPercentage`.
3. **Run your tests once with `--illegal-final-field-mutation=deny` on 26 or later.** Mocking, DI and serialisation libraries are the usual offenders ([11](11-recap-26.md)).
4. **Compile everything on 26 and 27.** `java.applet`, `JApplet` and `Thread.stop()` are gone ([73](output/73-removed-in-26.txt)).
5. **Test TLS through your real network path.** JDK 27 clients send a 1573-byte ClientHello ([04](04-post-quantum-tls.md)). If a middlebox objects, `-Djdk.tls.namedGroups=x25519,secp256r1` restores the old behaviour.
6. **Search for hard-coded object sizes.** Estimators, capacity plans and tests that assume a 12-byte header ([03](03-compact-headers.md)).
7. **Do not ship preview APIs from a library.** Structured concurrency, lazy constants, PEM and primitive patterns changed shape on *every* release in the lane ([12](12-lanes-25-to-29.md)).
8. **Check custom JFR redaction settings.** A list without `+` replaces the defaults ([09](09-jfr-redaction-and-vector.md)).
9. **Rebuild AOT caches per JDK and per collector** ([11](11-recap-26.md)).
10. **Decide whether to move at all.** 26 and 27 are non-LTS: 26's premier support ends September 2026 and 27's in March 2027. Staying on 25 and planning for 29 is a legitimate answer ([12](12-lanes-25-to-29.md)).
Prev: [12. Version lanes](12-lanes-25-to-29.md) &middot; [Back to the README](../README.md)
@@ -0,0 +1,43 @@
# JEP 523: which collector does the JVM pick when you do not choose one?
# Same class files, same vendor (Amazon Corretto), same 2 GB memory limit. Only the JDK version and the CPU limit change.
=== amazoncorretto:26 --cpus=1 --memory=2g
$ docker run --rm --cpus=1 --memory=2g amazoncorretto:26 java -cp /app GcReport
java.version = 26.0.2.1
availableCpus = 1
maxHeapMB = 511
collector beans = Copy, MarkSweepCompact
UseG1GC = false (origin DEFAULT)
UseSerialGC = true (origin ERGONOMIC)
UseParallelGC = false (origin DEFAULT)
=== amazoncorretto:27 --cpus=1 --memory=2g
$ docker run --rm --cpus=1 --memory=2g amazoncorretto:27 java -cp /app GcReport
java.version = 27
availableCpus = 1
maxHeapMB = 512
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
UseG1GC = true (origin ERGONOMIC)
UseSerialGC = false (origin DEFAULT)
UseParallelGC = false (origin DEFAULT)
=== amazoncorretto:26 --cpus=2 --memory=2g
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:26 java -cp /app GcReport
java.version = 26.0.2.1
availableCpus = 2
maxHeapMB = 512
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
UseG1GC = true (origin ERGONOMIC)
UseSerialGC = false (origin DEFAULT)
UseParallelGC = false (origin DEFAULT)
=== amazoncorretto:27 --cpus=2 --memory=2g
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:27 java -cp /app GcReport
java.version = 27
availableCpus = 2
maxHeapMB = 512
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
UseG1GC = true (origin ERGONOMIC)
UseSerialGC = false (origin DEFAULT)
UseParallelGC = false (origin DEFAULT)
+43
View File
@@ -0,0 +1,43 @@
# The other half of the old rule: memory. The old heuristic wanted at least 2 CPUs AND about 1792 MB.
# With 2 CPUs the CPU half of the rule is satisfied, so only the memory limit differs between these runs.
=== amazoncorretto:26 --cpus=2 --memory=1g
$ docker run --rm --cpus=2 --memory=1g amazoncorretto:26 java -cp /app GcReport
java.version = 26.0.2.1
availableCpus = 2
maxHeapMB = 255
collector beans = Copy, MarkSweepCompact
UseG1GC = false (origin DEFAULT)
UseSerialGC = true (origin ERGONOMIC)
UseParallelGC = false (origin DEFAULT)
=== amazoncorretto:27 --cpus=2 --memory=1g
$ docker run --rm --cpus=2 --memory=1g amazoncorretto:27 java -cp /app GcReport
java.version = 27
availableCpus = 2
maxHeapMB = 256
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
UseG1GC = true (origin ERGONOMIC)
UseSerialGC = false (origin DEFAULT)
UseParallelGC = false (origin DEFAULT)
=== amazoncorretto:26 --cpus=2 --memory=2g
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:26 java -cp /app GcReport
java.version = 26.0.2.1
availableCpus = 2
maxHeapMB = 512
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
UseG1GC = true (origin ERGONOMIC)
UseSerialGC = false (origin DEFAULT)
UseParallelGC = false (origin DEFAULT)
=== amazoncorretto:27 --cpus=2 --memory=2g
$ docker run --rm --cpus=2 --memory=2g amazoncorretto:27 java -cp /app GcReport
java.version = 27
availableCpus = 2
maxHeapMB = 512
collector beans = G1 Young Generation, G1 Concurrent GC, G1 Old Generation
UseG1GC = true (origin ERGONOMIC)
UseSerialGC = false (origin DEFAULT)
UseParallelGC = false (origin DEFAULT)
+11
View File
@@ -0,0 +1,11 @@
# The opt-out still works on 27: an explicit choice always wins over the JVM's.
=== amazoncorretto:27 --cpus=1 --memory=2g -XX:+UseSerialGC
$ docker run --rm --cpus=1 --memory=2g amazoncorretto:27 java -XX:+UseSerialGC -cp /app GcReport
java.version = 27
availableCpus = 1
maxHeapMB = 511
collector beans = Copy, MarkSweepCompact
UseG1GC = false (origin DEFAULT)
UseSerialGC = true (origin VM_CREATION)
UseParallelGC = false (origin DEFAULT)
+51
View File
@@ -0,0 +1,51 @@
# Allocation workload, 1 CPU / 2 GB container, 4000 batches x 50,000 objects, 3 runs per configuration (indicative, not a benchmark)
# 'default' means nobody passed a collector flag.
=== amazoncorretto:26 default run 1
wall time = 5980 ms
gc count / time = 1275 / 1831 ms
slowest batch = 115 ms
peak RSS = 557 MB
=== amazoncorretto:26 default run 2
wall time = 6145 ms
gc count / time = 1275 / 1872 ms
slowest batch = 142 ms
peak RSS = 557 MB
=== amazoncorretto:26 default run 3
wall time = 6402 ms
gc count / time = 1275 / 1861 ms
slowest batch = 129 ms
peak RSS = 557 MB
=== amazoncorretto:27 default run 1
wall time = 8196 ms
gc count / time = 140 / 1955 ms
slowest batch = 39 ms
peak RSS = 547 MB
=== amazoncorretto:27 default run 2
wall time = 8214 ms
gc count / time = 140 / 1970 ms
slowest batch = 41 ms
peak RSS = 547 MB
=== amazoncorretto:27 default run 3
wall time = 8432 ms
gc count / time = 140 / 2004 ms
slowest batch = 35 ms
peak RSS = 546 MB
=== amazoncorretto:27 -XX:+UseSerialGC run 1
wall time = 5306 ms
gc count / time = 1264 / 1669 ms
slowest batch = 125 ms
peak RSS = 557 MB
=== amazoncorretto:27 -XX:+UseSerialGC run 2
wall time = 4972 ms
gc count / time = 1264 / 1599 ms
slowest batch = 119 ms
peak RSS = 557 MB
=== amazoncorretto:27 -XX:+UseSerialGC run 3
wall time = 5698 ms
gc count / time = 1264 / 1713 ms
slowest batch = 128 ms
peak RSS = 557 MB
+51
View File
@@ -0,0 +1,51 @@
# Allocation workload, 1 CPU / 2 GB container, 400 batches x 50,000 objects, 3 runs per configuration (indicative, not a benchmark)
# 'default' means nobody passed a collector flag.
=== amazoncorretto:26 default run 1
wall time = 531 ms
gc count / time = 683 / 160 ms
slowest batch = 25 ms
peak RSS = 102 MB
=== amazoncorretto:26 default run 2
wall time = 536 ms
gc count / time = 683 / 165 ms
slowest batch = 22 ms
peak RSS = 103 MB
=== amazoncorretto:26 default run 3
wall time = 564 ms
gc count / time = 683 / 161 ms
slowest batch = 36 ms
peak RSS = 102 MB
=== amazoncorretto:27 default run 1
wall time = 1052 ms
gc count / time = 26 / 204 ms
slowest batch = 26 ms
peak RSS = 492 MB
=== amazoncorretto:27 default run 2
wall time = 974 ms
gc count / time = 26 / 192 ms
slowest batch = 25 ms
peak RSS = 492 MB
=== amazoncorretto:27 default run 3
wall time = 1041 ms
gc count / time = 26 / 234 ms
slowest batch = 36 ms
peak RSS = 492 MB
=== amazoncorretto:27 -XX:+UseSerialGC run 1
wall time = 528 ms
gc count / time = 668 / 147 ms
slowest batch = 23 ms
peak RSS = 98 MB
=== amazoncorretto:27 -XX:+UseSerialGC run 2
wall time = 522 ms
gc count / time = 668 / 157 ms
slowest batch = 27 ms
peak RSS = 98 MB
=== amazoncorretto:27 -XX:+UseSerialGC run 3
wall time = 588 ms
gc count / time = 668 / 157 ms
slowest batch = 48 ms
peak RSS = 97 MB
+41
View File
@@ -0,0 +1,41 @@
# JDK 27, G1 (the default), 1 CPU / 2 GB container, 400 batches, 2 runs per configuration
=== flags that differ between 26 and 27 (from -XX:+PrintFlagsFinal)
--- amazoncorretto:26
uint ConcGCThreads = 0 {product} {default}
uintx MaxHeapFreeRatio = 70 {manageable} {default}
size_t MaxHeapSize = 536870912 {product} {ergonomic}
uintx MinHeapFreeRatio = 40 {manageable} {default}
uint ParallelGCThreads = 0 {product} {default}
bool UseCompactObjectHeaders = false {product lp64_product} {default}
bool UseCompressedClassPointers = true {product} {default}
--- amazoncorretto:27
uint ConcGCThreads = 1 {product} {ergonomic}
uint G1IHOP = 45 {product} {default}
uintx MaxHeapFreeRatio = 100 {manageable} {default}
size_t MaxHeapSize = 536870912 {product} {ergonomic}
uintx MinHeapFreeRatio = 0 {manageable} {default}
uint ParallelGCThreads = 1 {product} {default}
bool UseCompactObjectHeaders = true {product lp64_product} {default}
=== amazoncorretto:27 flags: [none]
wall time = 1064 ms
gc count / time = 26 / 208 ms
peak RSS = 492 MB
wall time = 1078 ms
gc count / time = 26 / 202 ms
peak RSS = 493 MB
=== amazoncorretto:27 flags: [-XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70]
wall time = 1115 ms
gc count / time = 26 / 268 ms
peak RSS = 492 MB
wall time = 982 ms
gc count / time = 26 / 199 ms
peak RSS = 492 MB
=== amazoncorretto:27 flags: [-Xmx256m]
wall time = 976 ms
gc count / time = 37 / 240 ms
peak RSS = 294 MB
wall time = 957 ms
gc count / time = 37 / 261 ms
peak RSS = 294 MB
+82
View File
@@ -0,0 +1,82 @@
java.version = 26.0.2.1
# VM mode: 64 bits
# Compressed references (oops): 0-bit shift
# Compressed class pointers: 0-bit shift and 0x3E000000 base
# Object alignment: 8 bytes
# ref, bool, byte, char, shrt, int, flt, lng, dbl
# Field sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array base offsets: 16, 16, 16, 16, 16, 16, 16, 16, 16
=== java.lang.Object
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== Point { int x; int y; }
HeaderDemo$Point object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int Point.x N/A
16 4 int Point.y N/A
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== Order { long id; int quantity; boolean paid; String customer; }
HeaderDemo$Order object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int Order.quantity N/A
16 8 long Order.id N/A
24 1 boolean Order.paid N/A
25 3 (alignment/padding gap)
28 4 java.lang.String Order.customer N/A
Instance size: 32 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
=== java.lang.Long and java.lang.Integer (boxed values)
java.lang.Long object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x001a15d0
12 4 (alignment/padding gap)
16 8 long Long.value 123456789
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x0019f570
12 4 int Integer.value 123456
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
=== int[3]
[I object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x00174360
12 4 (array length) 3
16 12 int [I.<elements> N/A
28 4 (object alignment gap)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== What it adds up to: one million objects
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
+79
View File
@@ -0,0 +1,79 @@
java.version = 27
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
# VM mode: 64 bits
# Lilliput VM detected (experimental)
# Compressed references (oops): 3-bit shift
# Compressed class pointers: 3-bit shift
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Object alignment: 8 bytes
# ref, bool, byte, char, shrt, int, flt, lng, dbl
# Field sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array base offsets: 12, 12, 12, 12, 12, 12, 12, 16, 16
=== java.lang.Object
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
Instance size: 8 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
=== Point { int x; int y; }
HeaderDemo$Point object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 int Point.x N/A
12 4 int Point.y N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
=== Order { long id; int quantity; boolean paid; String customer; }
HeaderDemo$Order object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 8 long Order.id N/A
16 4 int Order.quantity N/A
20 1 boolean Order.paid N/A
21 3 (alignment/padding gap)
24 4 java.lang.String Order.customer N/A
28 4 (object alignment gap)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
=== java.lang.Long and java.lang.Integer (boxed values)
java.lang.Long object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x001aa80000000001 (Lilliput)
8 8 long Long.value 123456789
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x001a840000000001 (Lilliput)
8 4 int Integer.value 123456
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== int[3]
[I object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0017840000000001 (Lilliput)
8 4 (array length) 3
12 12 int [I.<elements> N/A
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
=== What it adds up to: one million objects
1,000,000 boxed Integer values in an ArrayList : 20,861,984 bytes
1,000,000 boxed Long values in an ArrayList : 20,861,984 bytes
1,000,000 Point objects in an ArrayList : 20,861,984 bytes
+86
View File
@@ -0,0 +1,86 @@
java.version = 27
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
# VM mode: 64 bits
# Compressed references (oops): 3-bit shift
# Compressed class pointers: 3-bit shift
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Object alignment: 8 bytes
# ref, bool, byte, char, shrt, int, flt, lng, dbl
# Field sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array base offsets: 16, 16, 16, 16, 16, 16, 16, 16, 16
=== java.lang.Object
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== Point { int x; int y; }
HeaderDemo$Point object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int Point.x N/A
16 4 int Point.y N/A
20 4 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== Order { long id; int quantity; boolean paid; String customer; }
HeaderDemo$Order object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 4 int Order.quantity N/A
16 8 long Order.id N/A
24 1 boolean Order.paid N/A
25 3 (alignment/padding gap)
28 4 java.lang.String Order.customer N/A
Instance size: 32 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
=== java.lang.Long and java.lang.Integer (boxed values)
java.lang.Long object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000009 (non-biasable; age: 1)
8 4 (object header: class) 0x001aa208
12 4 (alignment/padding gap)
16 8 long Long.value 123456789
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x001a7db8
12 4 int Integer.value 123456
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
=== int[3]
[I object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x00176c00
12 4 (array length) 3
16 12 int [I.<elements> N/A
28 4 (object alignment gap)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
=== What it adds up to: one million objects
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
+11
View File
@@ -0,0 +1,11 @@
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
Exception in thread "main" java.lang.RuntimeException: Cannot get the field offset, try with -Djol.magicFieldOffset=true
at org.openjdk.jol.vm.HotspotUnsafe.fieldOffset(HotspotUnsafe.java:533)
at org.openjdk.jol.layouters.CurrentLayouter.layout(CurrentLayouter.java:70)
at org.openjdk.jol.info.ClassLayout.parseClass(ClassLayout.java:72)
at org.openjdk.jol.info.ClassLayout.parseClass(ClassLayout.java:56)
at RecordProbe.main(RecordProbe.java:12)
Caused by: java.lang.UnsupportedOperationException: can't get field offset on a record class: private final int RecordProbe$Coordinates.lat
at jdk.unsupported/sun.misc.Unsafe.objectFieldOffset(Unsafe.java:903)
at org.openjdk.jol.vm.HotspotUnsafe.fieldOffset(HotspotUnsafe.java:526)
... 4 more
+9
View File
@@ -0,0 +1,9 @@
# WARNING: Unable to attach Serviceability Agent. sun.jvm.hotspot.runtime.VM.isCompressedKlassPointersEnabled()
RecordProbe$Coordinates object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 int Coordinates.lat N/A
12 4 int Coordinates.lon N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
+31
View File
@@ -0,0 +1,31 @@
# Instance sizes in bytes, extracted from 10-, 11- and 12- transcripts
--- 10-headers-jdk26
=== java.lang.Object -> Instance size: 16 bytes
=== Point { int x; int y; } -> Instance size: 24 bytes
=== Order { long id; int quantity; boolean paid; String customer; } -> Instance size: 32 bytes
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 24 bytes
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
=== int[3] -> Instance size: 32 bytes
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
--- 11-headers-jdk27
=== java.lang.Object -> Instance size: 8 bytes
=== Point { int x; int y; } -> Instance size: 16 bytes
=== Order { long id; int quantity; boolean paid; String customer; } -> Instance size: 32 bytes
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
=== int[3] -> Instance size: 24 bytes
1,000,000 boxed Integer values in an ArrayList : 20,861,984 bytes
1,000,000 boxed Long values in an ArrayList : 20,861,984 bytes
1,000,000 Point objects in an ArrayList : 20,861,984 bytes
--- 12-headers-jdk27-opt-out
=== java.lang.Object -> Instance size: 16 bytes
=== Point { int x; int y; } -> Instance size: 24 bytes
=== Order { long id; int quantity; boolean paid; String customer; } -> Instance size: 32 bytes
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 24 bytes
=== java.lang.Long and java.lang.Integer (boxed values) -> Instance size: 16 bytes
=== int[3] -> Instance size: 32 bytes
1,000,000 boxed Integer values in an ArrayList : 20,861,992 bytes
1,000,000 boxed Long values in an ArrayList : 28,861,992 bytes
1,000,000 Point objects in an ArrayList : 28,861,992 bytes
+6
View File
@@ -0,0 +1,6 @@
$ javac --enable-preview --release 27 Dominated.java (JDK 27)
Dominated.java:9: error: this case label is dominated by a preceding case label
case int primitive -> "an int"; // dominated: every Integer is already caught above
^
1 error
exit=1
+16
View File
@@ -0,0 +1,16 @@
$ javac --enable-preview --release 26 Lazy26.java (JDK 26)
exit=0
$ javac --enable-preview --release 27 Lazy26.java (JDK 27)
Lazy26.java:12: error: cannot find symbol
if (!GREETING.isInitialized()) {
^
symbol: method isInitialized()
location: variable GREETING of type LazyConstant<String>
Lazy26.java:13: error: cannot find symbol
System.out.println("not yet: " + GREETING.orElse("<unset>"));
^
symbol: method orElse(String)
location: variable GREETING of type LazyConstant<String>
2 errors
exit=1
+19
View File
@@ -0,0 +1,19 @@
$ javac --enable-preview --release 26 Pem26Style.java (JDK 26)
exit=0
$ javac --enable-preview --release 27 Pem26Style.java (JDK 27)
Pem26Style.java:1: error: cannot find symbol
import java.security.DEREncodable;
^
symbol: class DEREncodable
location: package java.security
Pem26Style.java:20: error: cannot find symbol
PEMDecoder viaProvider = PEMDecoder.of().withFactory(provider); // 2
^
symbol: method withFactory(Provider)
location: class PEMDecoder
Pem26Style.java:22: error: incompatible types: byte[] cannot be converted to String
String base64Text = pem.content(); // 3
^
3 errors
exit=1
@@ -0,0 +1,19 @@
$ javac --enable-preview --release 26 StructuredScope26.java (JDK 26)
exit=0
$ javac --enable-preview --release 27 StructuredScope26.java (JDK 27)
StructuredScope26.java:12: error: wrong number of type arguments; required 3
try (StructuredTaskScope<String, List<String>> scope =
^
StructuredScope26.java:17: error: cannot find symbol
} catch (StructuredTaskScope.FailedException e) {
^
symbol: class FailedException
location: interface StructuredTaskScope
StructuredScope26.java:19: error: cannot find symbol
} catch (StructuredTaskScope.TimeoutException e) {
^
symbol: class TimeoutException
location: interface StructuredTaskScope
3 errors
exit=1
+14
View File
@@ -0,0 +1,14 @@
$ java --enable-preview StructuredDemo (JDK 27)
--- allSuccessfulOrThrow(): results as a List, failures as ExecutionException
results = [price=42, stock=7]
CHECK ok : join() returns the subtask results in fork order
caught java.util.concurrent.ExecutionException with cause java.lang.IllegalStateException: inventory service is down
CHECK ok : a failing subtask surfaces as ExecutionException(cause)
--- allSuccessfulOrThrow(Function): join() throws the exception type you choose
caught OrderFailed: order failed: inventory service is down
CHECK ok : the mapped exception wraps the original failure
--- withTimeout(...): what happens when the scope runs out of time
caught java.util.concurrent.ExecutionException
cause: java.util.concurrent.StructuredTaskScope$CancelledByTimeoutException
CHECK ok : a timeout is reported as CancelledByTimeoutException
exit=0
+22
View File
@@ -0,0 +1,22 @@
$ java --enable-preview LazyDemo (JDK 27)
--- a lazy constant computes once, on first use
toString before first get(): jdk.internal.lang.LazyConstantImpl@...[computing function=LazyDemo$$Lambda/0x...@...]
(loading settings on main)
toString after first get(): jdk.internal.lang.LazyConstantImpl@...[Settings[region=ap-south-1, poolSize=16]]
CHECK ok : get() returns the same instance every time
CHECK ok : the supplier ran exactly once
--- and once even when 64 threads race for it
CHECK ok : 64 racing threads triggered exactly one initialisation
--- List.ofLazy: each element is computed on first access
computed so far: 0
squares.get(3) = square(3)=9
squares.get(3) = square(3)=9 (second read)
CHECK ok : only element 3 was ever computed
--- Map.ofLazy: the keys are fixed, the values are computed on first access
lengths.get("gamma") = 5
CHECK ok : only the requested key's value was computed
--- Set.ofLazy (new in 27): membership is decided by a predicate, on demand
primes.contains(7) = true, primes.contains(6) = false
CHECK ok : the lazy set answers membership using the predicate
primes = [2, 3, 5, 7]
exit=0
+28
View File
@@ -0,0 +1,28 @@
$ java --enable-preview PrimitivePatterns (JDK 27)
--- instanceof <primitive>: an exact-conversion test, not a cast
100 instanceof byte = true
300 instanceof byte = false (a byte holds -128..127)
CHECK ok : 100 converts to byte exactly, 300 does not
binding: byte narrow = 100
16_777_216 instanceof float = true
16_777_217 instanceof float = false (float has a 24-bit significand)
CHECK ok : int to float is exact up to 2^24 and lossy after
--- switch on a long, with pattern labels and a guard
classify(0) = zero
classify(1) = one
classify(-7) = negative (-7)
classify(9000000000) = positive (9000000000)
CHECK ok : long switch dispatches on constants, then patterns
--- switch on an Object: an Integer matches 'case int'
describe(Integer 7) = an int 7
describe(Integer 5000) = a large int 5000
describe(Long 7) = a long 7
describe(Double 2.5) = a double 2.5
describe(String hello) = a String of length 5
describe(Character x) = something else: x
CHECK ok : an Integer 5000 matches case int with a guard
--- record pattern with a narrowing primitive component
Reading[celsius=36] -> fits in a byte: 36
Reading[celsius=4000] -> needs a full int: 4000
CHECK ok : Reading(byte c) matches 36 but not 4000
exit=0
+29
View File
@@ -0,0 +1,29 @@
$ java --enable-preview PemDemo (JDK 27)
--- public key, encoded
-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----
--- private key, encoded
-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----
CHECK ok : a PublicKey encodes as BEGIN PUBLIC KEY
CHECK ok : a PrivateKey encodes as BEGIN PRIVATE KEY (PKCS#8)
CHECK ok : the decoded public key equals the original
CHECK ok : the decoded private key equals the original
--- decode(String) returns whatever the header says it is
-----BEGIN PUBLIC KEY----- -> PublicKey (EC)
-----BEGIN PRIVATE KEY----- -> PrivateKey (EC)
--- encrypted private key
-----BEGIN ENCRYPTED PRIVATE KEY-----
CHECK ok : withEncryption produces ENCRYPTED PRIVATE KEY
decoded with no password: javax.crypto.EncryptedPrivateKeyInfo
CHECK ok : without a password you get the still-encrypted structure back
CHECK ok : with the password you get the original private key
--- PEM(type, byte[]) does NOT Base64-encode: the bytes go in as they are
-----BEGIN ANKURM DEMO-----
hello, pem
-----END ANKURM DEMO-----
CHECK ok : raw bytes appear verbatim between the header and footer
--- give it the Base64 text and it round-trips
-----BEGIN ANKURM DEMO-----
aGVsbG8sIHBlbQ==
-----END ANKURM DEMO-----
CHECK ok : type, Base64 text (content()) and decoded payload (decode()) all survive
exit=0
+7
View File
@@ -0,0 +1,7 @@
$ java --add-modules jdk.incubator.vector VectorDemo (JDK 27)
java.version = 27
preferred species = Species[float, 16, S_512_BIT] (16 float lanes, machine dependent)
scalar dot = 749999.6
vector dot = 749999.6
CHECK ok : vector and scalar dot products agree to within 0.1%
exit=0
+21
View File
@@ -0,0 +1,21 @@
# Who talks to whom: the key-exchange group each pairing actually negotiates (TLS 1.3, loopback)
=== client JDK 26 -> server JDK 26
client offered : "named groups": [x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
server selected: x25519
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
=== client JDK 27 -> server JDK 27
client offered : "named groups": [X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096]
server selected: X25519MLKEM768
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
=== client JDK 27 -> server JDK 26
client offered : "named groups": [X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096]
server selected: x25519
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
=== client JDK 26 -> server JDK 27
client offered : "named groups": [x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
server selected: x25519
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
+19
View File
@@ -0,0 +1,19 @@
# Size of the first TLS record (the ClientHello) each JDK sends
=== JDK 26
java.version = 26.0.2.1
record type = 0x16 (0x16 = handshake)
ClientHello record = 417 bytes (+5 byte record header)
fits one TCP segment = yes (1460-byte MSS on a 1500-byte MTU)
=== JDK 27
java.version = 27
record type = 0x16 (0x16 = handshake)
ClientHello record = 1573 bytes (+5 byte record header)
fits one TCP segment = NO (1460-byte MSS on a 1500-byte MTU)
=== JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 (post-quantum group removed)
java.version = 27
record type = 0x16 (0x16 = handshake)
ClientHello record = 408 bytes (+5 byte record header)
fits one TCP segment = yes (1460-byte MSS on a 1500-byte MTU)
+6
View File
@@ -0,0 +1,6 @@
# Opting out on JDK 27: a client that does not offer the hybrid group
=== client JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 -> server JDK 27
client offered : "named groups": [x25519, secp256r1]
server selected: x25519
handshake complete: TLSv1.3 TLS_AES_256_GCM_SHA384
+41
View File
@@ -0,0 +1,41 @@
# The process under test: java -Dapi.token=abc123 -Dregion=ap-south-1 ... Idle --password=hunter2 --user=ankur
# with DB_PASSWORD=hunter2 in the environment. Which of those values reach the recording?
=== jdk26-default
jvmArguments = "-Dapi.token=abc123 -Dregion=ap-south-1 -XX:StartFlightRecording:filename=<file>"
javaArguments = "Idle --password=hunter2 --user=ankur"
key = "region" value = "ap-south-1"
key = "api.token" value = "abc123"
key = "DB_PASSWORD" value = "hunter2"
=== jdk27-default
jvmArguments = "[REDACTED] -Dregion=ap-south-1 -XX:StartFlightRecording:filename=<file>"
javaArguments = "Idle [REDACTED] --user=ankur"
key = "region" value = "ap-south-1"
key = "api.token" value = "[REDACTED]"
key = "DB_PASSWORD" value = "[REDACTED]"
=== jdk27-own-list-replaces-defaults
-XX:FlightRecorderOptions:redact-argument=--user*,redact-key=region
jvmArguments = "-Dapi.token=abc123 -Dregion=[REDACTED] -XX:FlightRecorderOptions:redact-argument=[REDACTED],redact-key=region -XX:StartFlightRecording:filename=<file>"
javaArguments = "Idle --password=hunter2 [REDACTED]"
key = "region" value = "[REDACTED]"
key = "api.token" value = "abc123"
key = "DB_PASSWORD" value = "hunter2"
=== jdk27-plus-extends-defaults
-XX:FlightRecorderOptions:redact-argument=+--user*,redact-key=+region
jvmArguments = "[REDACTED] -Dregion=[REDACTED] -XX:FlightRecorderOptions:redact-argument=[REDACTED],redact-key=+region -XX:StartFlightRecording:filename=<file>"
javaArguments = "Idle [REDACTED] [REDACTED]"
key = "region" value = "[REDACTED]"
key = "api.token" value = "[REDACTED]"
key = "DB_PASSWORD" value = "[REDACTED]"
=== jdk27-none-disables-redaction
-XX:FlightRecorderOptions:redact-argument=none,redact-key=none
jvmArguments = "-Dapi.token=abc123 -Dregion=ap-south-1 -XX:FlightRecorderOptions:redact-argument=[REDACTED],redact-key=none -XX:StartFlightRecording:filename=<file>"
javaArguments = "Idle --password=hunter2 --user=ankur"
key = "region" value = "ap-south-1"
key = "api.token" value = "abc123"
key = "DB_PASSWORD" value = "hunter2"
+8
View File
@@ -0,0 +1,8 @@
# jdk.incubator.vector: what changed between 26 and 27 (JEP 529 was the 11th incubation in 26, JEP 537 is the 12th in 27)
## public classes removed
## public classes added
## public class changes
jdk.incubator.vector.VectorOperators
- public abstract class jdk.incubator.vector.VectorOperators {
+ public final class jdk.incubator.vector.VectorOperators {
+190
View File
@@ -0,0 +1,190 @@
# Public API removed/added/changed between JDK 26 and 27 (javap -public of every exported class; internal packages skipped)
## public classes removed
java.security.DEREncodable
java.util.concurrent.StructuredTaskScope$FailedException
java.util.concurrent.StructuredTaskScope$TimeoutException
## public classes added
com.sun.source.tree.VarTypeTree
java.security.BinaryEncodable
java.time.format.DateTimeFormatterPatternProvider
java.util.concurrent.StructuredTaskScope$CancelledByTimeoutException
javax.crypto.CryptoException
## public class changes
com.sun.source.tree.Tree$Kind
+ public static final com.sun.source.tree.Tree$Kind VAR_TYPE;
com.sun.source.tree.TreeVisitor
+ public abstract R visitVarType(com.sun.source.tree.VarTypeTree, P);
com.sun.source.util.DocSourcePositions
- public abstract long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
- public abstract long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
+ public abstract long getEndPosition(com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
+ public abstract long getStartPosition(com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
+ public default long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
+ public default long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.doctree.DocCommentTree, com.sun.source.doctree.DocTree);
com.sun.source.util.SimpleTreeVisitor
+ public R visitVarType(com.sun.source.tree.VarTypeTree, P);
com.sun.source.util.SourcePositions
- public abstract long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
- public abstract long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
+ public abstract long getEndPosition(com.sun.source.tree.Tree);
+ public abstract long getStartPosition(com.sun.source.tree.Tree);
+ public default long getEndPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
+ public default long getStartPosition(com.sun.source.tree.CompilationUnitTree, com.sun.source.tree.Tree);
com.sun.source.util.TreeScanner
+ public R visitVarType(com.sun.source.tree.VarTypeTree, P);
java.awt.KeyboardFocusManager
- public static java.awt.KeyboardFocusManager getCurrentKeyboardFocusManager();
+ public static synchronized java.awt.KeyboardFocusManager getCurrentKeyboardFocusManager();
java.awt.geom.Arc2D
+ public java.awt.Rectangle getBounds();
java.lang.LazyConstant
- public abstract T orElse(T);
- public abstract boolean isInitialized();
java.lang.Math
+ public static double acosh(double);
+ public static double asinh(double);
+ public static double atanh(double);
java.lang.StrictMath
+ public static double acosh(double);
+ public static double asinh(double);
+ public static double atanh(double);
java.lang.String
+ public int encodedLength(java.nio.charset.Charset);
java.lang.classfile.ClassFile
+ public static final int JAVA_27_VERSION;
java.lang.classfile.instruction.IncrementInstruction
+ public static java.lang.classfile.instruction.IncrementInstruction of(java.lang.classfile.Opcode, int, int);
java.lang.foreign.MemorySegment
+ public abstract java.lang.String getString(long, java.nio.charset.Charset, long);
+ public static long copy(java.lang.String, java.nio.charset.Charset, int, java.lang.foreign.MemorySegment, long, int);
java.lang.foreign.SegmentAllocator
+ public default java.lang.foreign.MemorySegment allocateFrom(java.lang.String, java.nio.charset.Charset, int, int);
java.lang.reflect.ClassFileFormatVersion
+ public static final java.lang.reflect.ClassFileFormatVersion RELEASE_27;
java.lang.reflect.Executable
- public abstract java.lang.String toGenericString();
+ public java.lang.String toGenericString();
java.math.BigDecimal
+ public java.math.BigDecimal rootn(int, java.math.MathContext);
java.security.AsymmetricKey
- public interface java.security.AsymmetricKey extends java.security.Key,java.security.DEREncodable {
+ public interface java.security.AsymmetricKey extends java.security.Key,java.security.BinaryEncodable {
java.security.KeyPair
- public final class java.security.KeyPair implements java.io.Serializable,java.security.DEREncodable {
+ public final class java.security.KeyPair implements java.io.Serializable,java.security.BinaryEncodable {
java.security.KeyStore
+ public final java.time.Instant getCreationInstant(java.lang.String) throws java.security.KeyStoreException;
java.security.KeyStoreSpi
+ public java.time.Instant engineGetCreationInstant(java.lang.String);
java.security.PEM
- public final class java.security.PEM extends java.lang.Record implements java.security.DEREncodable {
+ public final class java.security.PEM implements java.security.BinaryEncodable {
- public final boolean equals(java.lang.Object);
- public final byte[] decode();
- public final int hashCode();
- public final java.lang.String toString();
- public java.lang.String content();
+ public byte[] content();
+ public byte[] decode();
+ public java.lang.String toString();
+ public java.security.PEM(java.lang.String, byte[]);
+ public java.security.PEM(java.lang.String, byte[], byte[]);
java.security.PEMDecoder
- public <S extends java.security.DEREncodable> S decode(java.io.InputStream, java.lang.Class<S>) throws java.io.IOException;
- public <S extends java.security.DEREncodable> S decode(java.lang.String, java.lang.Class<S>);
- public java.security.DEREncodable decode(java.io.InputStream) throws java.io.IOException;
- public java.security.DEREncodable decode(java.lang.String);
- public java.security.PEMDecoder withFactory(java.security.Provider);
+ public <S extends java.security.BinaryEncodable> S decode(java.io.InputStream, java.lang.Class<S>) throws java.io.IOException;
+ public <S extends java.security.BinaryEncodable> S decode(java.lang.String, java.lang.Class<S>);
+ public java.security.BinaryEncodable decode(java.io.InputStream) throws java.io.IOException;
+ public java.security.BinaryEncodable decode(java.lang.String);
+ public java.security.PEMDecoder withFactoriesOf(java.security.Provider);
java.security.PEMEncoder
- public byte[] encode(java.security.DEREncodable);
- public java.lang.String encodeToString(java.security.DEREncodable);
+ public byte[] encode(java.security.BinaryEncodable);
+ public java.lang.String encodeToString(java.security.BinaryEncodable);
java.security.cert.X509CRL
- public abstract class java.security.cert.X509CRL extends java.security.cert.CRL implements java.security.cert.X509Extension,java.security.DEREncodable {
+ public abstract class java.security.cert.X509CRL extends java.security.cert.CRL implements java.security.cert.X509Extension,java.security.BinaryEncodable {
java.security.cert.X509Certificate
- public abstract class java.security.cert.X509Certificate extends java.security.cert.Certificate implements java.security.cert.X509Extension,java.security.DEREncodable {
+ public abstract class java.security.cert.X509Certificate extends java.security.cert.Certificate implements java.security.cert.X509Extension,java.security.BinaryEncodable {
java.security.spec.PKCS8EncodedKeySpec
- public class java.security.spec.PKCS8EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.DEREncodable {
+ public class java.security.spec.PKCS8EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.BinaryEncodable {
java.security.spec.X509EncodedKeySpec
- public class java.security.spec.X509EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.DEREncodable {
+ public class java.security.spec.X509EncodedKeySpec extends java.security.spec.EncodedKeySpec implements java.security.BinaryEncodable {
java.util.EnumSet
+ public java.util.Spliterator<E> spliterator();
java.util.Locale
- public final java.lang.String getDisplayCountry();
- public final java.lang.String getDisplayLanguage();
- public final java.lang.String getDisplayName();
- public final java.lang.String getDisplayVariant();
+ public java.lang.String getDisplayCountry();
+ public java.lang.String getDisplayLanguage();
+ public java.lang.String getDisplayName();
+ public java.lang.String getDisplayVariant();
java.util.Set
+ public static <E> java.util.Set<E> ofLazy(java.util.Set<? extends E>, java.util.function.Predicate<? super E>);
java.util.concurrent.StructuredTaskScope
- public interface java.util.concurrent.StructuredTaskScope<T, R> extends java.lang.AutoCloseable {
+ public interface java.util.concurrent.StructuredTaskScope<T, R, R_X extends java.lang.Throwable> extends java.lang.AutoCloseable {
- public abstract R join() throws java.lang.InterruptedException;
- public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>);
- public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>, java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
- public static <T> java.util.concurrent.StructuredTaskScope<T, java.lang.Void> open();
+ public abstract R join() throws R_X, java.lang.InterruptedException;
+ public static <T, R, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope<T, R, R_X> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R, R_X>);
+ public static <T, R, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope<T, R, R_X> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R, R_X>, java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
+ public static <T> java.util.concurrent.StructuredTaskScope<T, java.lang.Void, java.util.concurrent.ExecutionException> open();
+ public static <T> java.util.concurrent.StructuredTaskScope<T, java.lang.Void, java.util.concurrent.ExecutionException> open(java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
java.util.concurrent.StructuredTaskScope$Joiner
- public interface java.util.concurrent.StructuredTaskScope$Joiner<T, R> {
+ public interface java.util.concurrent.StructuredTaskScope$Joiner<T, R, R_X extends java.lang.Throwable> {
- public abstract R result() throws java.lang.Throwable;
- public default void onTimeout();
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T> anySuccessfulOrThrow();
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void> awaitAll();
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void> awaitAllSuccessfulOrThrow();
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>> allSuccessfulOrThrow();
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allUntil(java.util.function.Predicate<java.util.concurrent.StructuredTaskScope$Subtask<T>>);
+ public abstract R result() throws R_X;
+ public abstract R timeout() throws R_X;
+ public static <T, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope$Joiner<T, T, R_X> anySuccessfulOrThrow(java.util.function.Function<java.lang.Throwable, R_X>);
+ public static <T, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void, R_X> awaitAllSuccessfulOrThrow(java.util.function.Function<java.lang.Throwable, R_X>);
+ public static <T, R_X extends java.lang.Throwable> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>, R_X> allSuccessfulOrThrow(java.util.function.Function<java.lang.Throwable, R_X>);
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T, java.util.concurrent.ExecutionException> anySuccessfulOrThrow();
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.lang.Void, java.util.concurrent.ExecutionException> awaitAllSuccessfulOrThrow();
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>, java.util.concurrent.ExecutionException> allSuccessfulOrThrow();
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<java.util.concurrent.StructuredTaskScope$Subtask<T>>, java.lang.RuntimeException> allUntil(java.util.function.Predicate<? super java.util.concurrent.StructuredTaskScope$Subtask<T>>);
javax.crypto.EncryptedPrivateKeyInfo
- public class javax.crypto.EncryptedPrivateKeyInfo implements java.security.DEREncodable {
+ public class javax.crypto.EncryptedPrivateKeyInfo implements java.security.BinaryEncodable {
- public java.security.KeyPair getKeyPair(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
- public java.security.PrivateKey getKey(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
- public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[]);
- public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
- public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
+ public java.security.KeyPair getKeyPair(java.security.Key) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
+ public java.security.PrivateKey getKey(java.security.Key) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.BinaryEncodable, char[]);
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.BinaryEncodable, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.BinaryEncodable, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
javax.lang.model.SourceVersion
+ public static final javax.lang.model.SourceVersion RELEASE_27;
javax.swing.JEditorPane$JEditorPaneAccessibleHypertextSupport
+ public void changedUpdate(javax.swing.event.DocumentEvent);
+ public void insertUpdate(javax.swing.event.DocumentEvent);
+ public void removeUpdate(javax.swing.event.DocumentEvent);
jdk.incubator.vector.VectorOperators
- public abstract class jdk.incubator.vector.VectorOperators {
+ public final class jdk.incubator.vector.VectorOperators {
jdk.javadoc.doclet.Taglet
+ public default java.lang.String toString(java.util.List<? extends com.sun.source.doctree.DocTree>, javax.lang.model.element.Element, java.net.URI);
jdk.jfr.events.ContainerMemoryUsageEvent
+ public long hostMemoryUsage;
+197
View File
@@ -0,0 +1,197 @@
# Public API removed/added/changed between JDK 25 and 26
## public classes removed
java.applet.Applet
java.applet.Applet$AccessibleApplet
java.applet.AppletContext
java.applet.AppletStub
java.applet.AudioClip
java.beans.AppletInitializer
java.lang.StableValue
java.security.PEMRecord
javax.management.modelmbean.XMLParseException
javax.swing.JApplet
javax.swing.JApplet$AccessibleJApplet
## public classes added
java.lang.LazyConstant
java.net.http.HttpOption
java.net.http.HttpOption$Http3DiscoveryMode
java.net.http.HttpResponse$PushPromiseHandler$PushId
java.net.http.HttpResponse$PushPromiseHandler$PushId$Http3PushId
java.net.http.StreamLimitException
java.net.http.UnsupportedProtocolVersionException
java.security.PEM
javax.crypto.spec.HPKEParameterSpec
jdk.jfr.events.FinalFieldMutationEvent
jdk.jshell.SourceCodeAnalysis$CompletionContext
jdk.jshell.SourceCodeAnalysis$CompletionState
jdk.jshell.SourceCodeAnalysis$ElementSuggestion
jdk.jshell.SourceCodeAnalysis$ElementSuggestionConvertor
## public class changes
com.sun.net.httpserver.HttpExchange
+ public static final long RSPBODY_CHUNKED;
+ public static final long RSPBODY_EMPTY;
java.awt.Robot
+ public static final int DEFAULT_DELAY;
+ public static final int DEFAULT_STEP_LENGTH;
+ public synchronized void type(char);
+ public synchronized void type(int);
+ public void click();
+ public void click(int);
+ public void glide(int, int);
+ public void glide(int, int, int, int);
+ public void glide(int, int, int, int, int, int);
+ public void waitForIdle(int);
java.beans.Beans
- public static java.lang.Object instantiate(java.lang.ClassLoader, java.lang.String, java.beans.beancontext.BeanContext, java.beans.AppletInitializer) throws java.io.IOException, java.lang.ClassNotFoundException;
java.lang.Character$UnicodeBlock
+ public static final java.lang.Character$UnicodeBlock BERIA_ERFE;
+ public static final java.lang.Character$UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J;
+ public static final java.lang.Character$UnicodeBlock MISCELLANEOUS_SYMBOLS_SUPPLEMENT;
+ public static final java.lang.Character$UnicodeBlock SHARADA_SUPPLEMENT;
+ public static final java.lang.Character$UnicodeBlock SIDETIC;
+ public static final java.lang.Character$UnicodeBlock TAI_YO;
+ public static final java.lang.Character$UnicodeBlock TANGUT_COMPONENTS_SUPPLEMENT;
+ public static final java.lang.Character$UnicodeBlock TOLONG_SIKI;
java.lang.Character$UnicodeScript
+ public static final java.lang.Character$UnicodeScript BERIA_ERFE;
+ public static final java.lang.Character$UnicodeScript SIDETIC;
+ public static final java.lang.Character$UnicodeScript TAI_YO;
+ public static final java.lang.Character$UnicodeScript TOLONG_SIKI;
java.lang.Process
- public abstract class java.lang.Process {
+ public abstract class java.lang.Process implements java.io.Closeable {
+ public void close() throws java.io.IOException;
java.lang.String
+ public boolean equalsFoldCase(java.lang.String);
+ public int compareToFoldCase(java.lang.String);
+ public static final java.util.Comparator<java.lang.String> UNICODE_CASEFOLD_ORDER;
java.lang.Thread
- public final void stop();
java.lang.classfile.ClassFile
+ public static final int JAVA_26_VERSION;
java.lang.management.MemoryMXBean
+ public default long getTotalGcCpuTime();
java.lang.reflect.ClassFileFormatVersion
+ public static final java.lang.reflect.ClassFileFormatVersion RELEASE_26;
java.math.BigInteger
+ public java.math.BigInteger rootn(int);
+ public java.math.BigInteger[] rootnAndRemainder(int);
java.net.MulticastSocket
- public byte getTTL() throws java.io.IOException;
- public void send(java.net.DatagramPacket, byte) throws java.io.IOException;
- public void setTTL(byte) throws java.io.IOException;
java.net.http.HttpClient$Version
+ public static final java.net.http.HttpClient$Version HTTP_3;
java.net.http.HttpRequest
+ public <T> java.util.Optional<T> getOption(java.net.http.HttpOption<T>);
java.net.http.HttpRequest$BodyPublishers
+ public static java.net.http.HttpRequest$BodyPublisher ofFileChannel(java.nio.channels.FileChannel, long, long) throws java.io.IOException;
java.net.http.HttpRequest$Builder
+ public default <T> java.net.http.HttpRequest$Builder setOption(java.net.http.HttpOption<T>, T);
java.net.http.HttpResponse$PushPromiseHandler
+ public default void applyPushPromise(java.net.http.HttpRequest, java.net.http.HttpRequest, java.net.http.HttpResponse$PushPromiseHandler$PushId, java.util.function.Function<java.net.http.HttpResponse$BodyHandler<T>, java.util.concurrent.CompletableFuture<java.net.http.HttpResponse<T>>>);
+ public default void notifyAdditionalPromise(java.net.http.HttpRequest, java.net.http.HttpResponse$PushPromiseHandler$PushId);
java.nio.ByteOrder
- public final class java.nio.ByteOrder {
+ public final class java.nio.ByteOrder extends java.lang.Enum<java.nio.ByteOrder> {
- public java.lang.String toString();
+ public static java.nio.ByteOrder valueOf(java.lang.String);
+ public static java.nio.ByteOrder[] values();
java.security.SecureRandom
+ public long nextLong();
java.sql.Array
- public interface java.sql.Array {
+ public interface java.sql.Array extends java.lang.AutoCloseable {
+ public default void close() throws java.sql.SQLException;
java.sql.Blob
- public interface java.sql.Blob {
+ public interface java.sql.Blob extends java.lang.AutoCloseable {
+ public default void close() throws java.sql.SQLException;
java.sql.Clob
- public interface java.sql.Clob {
+ public interface java.sql.Clob extends java.lang.AutoCloseable {
+ public default void close() throws java.sql.SQLException;
java.sql.Connection
+ public default boolean isSimpleIdentifier(java.lang.String) throws java.sql.SQLException;
+ public default java.lang.String enquoteIdentifier(java.lang.String, boolean) throws java.sql.SQLException;
+ public default java.lang.String enquoteLiteral(java.lang.String) throws java.sql.SQLException;
+ public default java.lang.String enquoteNCharLiteral(java.lang.String) throws java.sql.SQLException;
java.sql.JDBCType
+ public static final java.sql.JDBCType DECFLOAT;
+ public static final java.sql.JDBCType JSON;
java.sql.SQLXML
- public interface java.sql.SQLXML {
+ public interface java.sql.SQLXML extends java.lang.AutoCloseable {
+ public default void close() throws java.sql.SQLException;
java.sql.Types
+ public static final int DECFLOAT;
+ public static final int JSON;
java.time.Duration
+ public static final java.time.Duration MAX;
+ public static final java.time.Duration MIN;
java.time.Instant
+ public java.time.Instant plusSaturating(java.time.Duration);
java.util.Comparator
+ public default <U extends T> U max(U, U);
+ public default <U extends T> U min(U, U);
java.util.List
+ public static <E> java.util.List<E> ofLazy(int, java.util.function.IntFunction<? extends E>);
java.util.Locale$IsoCountryCode
- public abstract class java.util.Locale$IsoCountryCode extends java.lang.Enum<java.util.Locale$IsoCountryCode> {
+ public final class java.util.Locale$IsoCountryCode extends java.lang.Enum<java.util.Locale$IsoCountryCode> {
java.util.Map
+ public static <K, V> java.util.Map<K, V> ofLazy(java.util.Set<? extends K>, java.util.function.Function<? super K, ? extends V>);
java.util.UUID
+ public static java.util.UUID ofEpochMillis(long);
java.util.concurrent.StructuredTaskScope
- public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>, java.util.function.Function<java.util.concurrent.StructuredTaskScope$Configuration, java.util.concurrent.StructuredTaskScope$Configuration>);
+ public static <T, R> java.util.concurrent.StructuredTaskScope<T, R> open(java.util.concurrent.StructuredTaskScope$Joiner<? super T, ? extends R>, java.util.function.UnaryOperator<java.util.concurrent.StructuredTaskScope$Configuration>);
java.util.concurrent.StructuredTaskScope$Joiner
- public default boolean onComplete(java.util.concurrent.StructuredTaskScope$Subtask<? extends T>);
- public default boolean onFork(java.util.concurrent.StructuredTaskScope$Subtask<? extends T>);
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T> anySuccessfulResultOrThrow();
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.stream.Stream<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allSuccessfulOrThrow();
- public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.stream.Stream<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allUntil(java.util.function.Predicate<java.util.concurrent.StructuredTaskScope$Subtask<? extends T>>);
+ public default boolean onComplete(java.util.concurrent.StructuredTaskScope$Subtask<T>);
+ public default boolean onFork(java.util.concurrent.StructuredTaskScope$Subtask<T>);
+ public default void onTimeout();
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, T> anySuccessfulOrThrow();
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<T>> allSuccessfulOrThrow();
+ public static <T> java.util.concurrent.StructuredTaskScope$Joiner<T, java.util.List<java.util.concurrent.StructuredTaskScope$Subtask<T>>> allUntil(java.util.function.Predicate<java.util.concurrent.StructuredTaskScope$Subtask<T>>);
java.util.concurrent.ThreadLocalRandom
+ public double nextGaussian();
javax.crypto.EncryptedPrivateKeyInfo
- public java.security.PrivateKey getKey(char[]) throws java.security.GeneralSecurityException;
- public java.security.PrivateKey getKey(java.security.Key, java.security.Provider) throws java.security.GeneralSecurityException;
- public static javax.crypto.EncryptedPrivateKeyInfo encryptKey(java.security.PrivateKey, char[]);
- public static javax.crypto.EncryptedPrivateKeyInfo encryptKey(java.security.PrivateKey, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
- public static javax.crypto.EncryptedPrivateKeyInfo encryptKey(java.security.PrivateKey, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
+ public java.security.KeyPair getKeyPair(char[]) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
+ public java.security.KeyPair getKeyPair(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
+ public java.security.PrivateKey getKey(char[]) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
+ public java.security.PrivateKey getKey(java.security.Key, java.security.Provider) throws java.security.NoSuchAlgorithmException, java.security.InvalidKeyException;
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[]);
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, char[], java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider);
+ public static javax.crypto.EncryptedPrivateKeyInfo encrypt(java.security.DEREncodable, java.security.Key, java.lang.String, java.security.spec.AlgorithmParameterSpec, java.security.Provider, java.security.SecureRandom);
javax.imageio.spi.ServiceRegistry
- public void finalize() throws java.lang.Throwable;
javax.lang.model.SourceVersion
+ public static final javax.lang.model.SourceVersion RELEASE_26;
javax.management.modelmbean.DescriptorSupport
- public javax.management.modelmbean.DescriptorSupport(java.lang.String) throws javax.management.MBeanException, javax.management.RuntimeOperationsException, javax.management.modelmbean.XMLParseException;
- public synchronized java.lang.String toXMLString();
javax.swing.RepaintManager
- public void addDirtyRegion(java.applet.Applet, int, int, int, int);
jdk.incubator.vector.VectorOperators
- public static final jdk.incubator.vector.VectorOperators$Binary SUADD;
+ public static final jdk.incubator.vector.VectorOperators$Associative SUADD;
jdk.jpackage.main.Main
- public int execute(java.io.PrintWriter, java.io.PrintWriter, java.lang.String...);
- public static final java.util.ResourceBundle I18N;
- public static void main(java.lang.String...) throws java.lang.Exception;
+ public static void main(java.lang.String...);
jdk.jshell.SourceCodeAnalysis
+ public abstract <S> java.util.List<S> completionSuggestions(java.lang.String, int, jdk.jshell.SourceCodeAnalysis$ElementSuggestionConvertor<S>);
jdk.jshell.SourceCodeAnalysis$Documentation
+ public default int activeParameterIndex();
+54
View File
@@ -0,0 +1,54 @@
# JVM options that JDK 27 removed or renamed. Each runs 'java <option> -version' on 26 and on 27.
=== -noverify
JDK 26:
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
JDK 27:
Unrecognized option: -noverify
Error: Could not create the Java Virtual Machine.
=== -Xverify:none
JDK 26:
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
JDK 27:
Unrecognized verification option: -Xverify:none
Error: Could not create the Java Virtual Machine.
=== -noclassgc
JDK 26:
Warning: -noclassgc option is deprecated and may be removed in a future release.
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
JDK 27:
Unrecognized option: -noclassgc
Error: Could not create the Java Virtual Machine.
=== -XX:+UseCompressedClassPointers
JDK 26:
OpenJDK 64-Bit Server VM warning: Option UseCompressedClassPointers was deprecated in version 25.0 and will likely be removed in a future release.
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
JDK 27:
OpenJDK 64-Bit Server VM warning: Ignoring option UseCompressedClassPointers; support was removed in 27.0
OpenJDK 64-Bit Server VM Corretto-27.0.0.35.1 (build 27+35-FR, mixed mode, sharing)
=== -XX:InitiatingHeapOccupancyPercent=40
JDK 26:
OpenJDK 64-Bit Server VM Corretto-26.0.2.11.1 (build 26.0.2.1+11-FR, mixed mode, sharing)
JDK 27:
OpenJDK 64-Bit Server VM warning: Option InitiatingHeapOccupancyPercent was deprecated in version 27.0 and will likely be removed in a future release. Use option G1IHOP instead.
OpenJDK 64-Bit Server VM Corretto-27.0.0.35.1 (build 27+35-FR, mixed mode, sharing)
=== -XX:G1IHOP=40
JDK 26:
Unrecognized VM option 'G1IHOP=40'
Error: Could not create the Java Virtual Machine.
JDK 27:
OpenJDK 64-Bit Server VM Corretto-27.0.0.35.1 (build 27+35-FR, mixed mode, sharing)
=== -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT (JVMCI and the Graal JIT hook were removed in 27)
JDK 26:
Cannot use JVMCI compiler: JVMCI compiler 'graal' specified by jvmci.Compiler not found
JDK 27:
Unrecognized VM option 'UseGraalJIT'
Error: Could not create the Java Virtual Machine.
+21
View File
@@ -0,0 +1,21 @@
# Behaviour changes that need no new API to observe
=== a script that pins the VFORK process launch mechanism
JDK 26: java -Djdk.lang.Process.launchMechanism=VFORK SpawnProbe
VFORK MODE DEPRECATED
The VFORK launch mechanism has been deprecated for being dangerous.
It will be removed in a future java version. Either remove the
JDK 27: java -Djdk.lang.Process.launchMechanism=VFORK SpawnProbe
The VFORK launch mechanism has been removed. Switching to FORK instead.
Please remove the jdk.lang.Process.launchMechanism property (preferred)
or use FORK mode instead (-Djdk.lang.Process.launchMechanism=FORK).
=== the legacy ISO 639 language codes property (iw, ji, in)
JDK 26: java -Djava.locale.useOldISOCodes=true Compat
java.version = 26.0.2.1
WARNING: The use of the system property "java.locale.useOldISOCodes" is deprecated. It will be removed in a future release of the JDK.
Locale("iw").language = iw (Hebrew; old code 'iw', current code 'he')
JDK 27: java -Djava.locale.useOldISOCodes=true Compat
java.version = 27
WARNING: The system property "java.locale.useOldISOCodes" is no longer supported. Any specified value will be ignored.
Locale("iw").language = he (Hebrew; old code 'iw', current code 'he')
+12
View File
@@ -0,0 +1,12 @@
# New public API in 27, each exercised once (found by diffing javap dumps, see 51-api-diff-26-to-27.txt)
Math.acosh(2) = 1.316958
Math.asinh(1) = 0.881374
Math.atanh(.5) = 0.549306
CHECK ok : acosh inverts cosh
"naïve café".length() = 10, encodedLength(UTF_8) = 12
CHECK ok : encodedLength matches getBytes().length without allocating the array
BigDecimal(27).rootn(3) = 3
CHECK ok : rootn(3) of 27 is 3
KeyStore.getCreationInstant("aes-key") returns an Instant: true
CHECK ok : KeyStore.getCreationInstant returns the entry's creation time as a java.time.Instant
CHECK ok : Set.ofLazy(Set, Predicate) exists (preview API)
+45
View File
@@ -0,0 +1,45 @@
# JEP 500: reflection writing to a final field, same class file behaviour on 25 and on 26
=== JDK 25 (class compiled --release 25)
field reads : 9090
port() returns : 8080
=== JDK 26, defaults
WARNING: Final field port in class FinalFieldMutation$Config has been mutated reflectively by class FinalFieldMutation in unnamed module @<hash> (file:<repo>/build/recap26/26/)
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
field reads : 9090
port() returns : 8080
=== JDK 26, --enable-final-field-mutation=ALL-UNNAMED (the opt-in the warning asks for)
field reads : 9090
port() returns : 8080
=== JDK 26, --illegal-final-field-mutation=deny (what a future default will look like)
setInt refused : class FinalFieldMutation (in unnamed module @<hash>) cannot set final field FinalFieldMutation$Config.port (in unnamed module @<hash>), unnamed module @<hash> is not allowed to mutate final fields
=== JDK 26, --illegal-final-field-mutation=debug (warning plus the offending stack trace)
Final field port in class FinalFieldMutation$Config has been mutated reflectively by class FinalFieldMutation in unnamed module @<hash> (file:<repo>/build/recap26/26/)
at java.base/java.lang.reflect.Field.postSetFinal(Field.java:1534)
at java.base/java.lang.reflect.Field.setFinal(Field.java:1449)
at java.base/java.lang.reflect.Field.setInt(Field.java:1134)
at FinalFieldMutation.main(FinalFieldMutation.java:22)
field reads : 9090
port() returns : 8080
=== JDK 26, JFR default settings, [no flag]: jfr print --events jdk.FinalFieldMutation
jdk.FinalFieldMutation
declaringClass = FinalFieldMutation$Config (classLoader = app)
fieldName = "port"
FinalFieldMutation.main(String[]) line: 22
events recorded: 1
=== JDK 26, JFR default settings, [--enable-final-field-mutation=ALL-UNNAMED]: jfr print --events jdk.FinalFieldMutation
jdk.FinalFieldMutation
declaringClass = FinalFieldMutation$Config (classLoader = app)
fieldName = "port"
FinalFieldMutation.main(String[]) line: 22
events recorded: 1
=== JDK 26, JFR default settings, [--illegal-final-field-mutation=deny]: jfr print --events jdk.FinalFieldMutation
events recorded: 0
+20
View File
@@ -0,0 +1,20 @@
# JEP 516: can the cache's archived heap objects be used? One row per (JDK, GC the cache was built under, GC at run time).
# 'heap objects' = the log says the archived module graph was used; 'unusable' = the JVM refused the whole cache.
JDK 25 cache built under G1 run under Serial -> heap objects used (full module graph: enabled)
JDK 25 cache built under G1 run under G1 -> heap objects used (full module graph: enabled)
JDK 25 cache built under G1 run under Z -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
JDK 25 cache built under Z run under Serial -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
JDK 25 cache built under Z run under G1 -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
JDK 25 cache built under Z run under Z -> cache used, heap objects NOT used (full module graph: disabled)
JDK 26 cache built under G1 run under Serial -> heap objects used (full module graph: enabled)
JDK 26 cache built under G1 run under G1 -> heap objects used (full module graph: enabled)
JDK 26 cache built under G1 run under Z -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
JDK 26 cache built under Z run under Serial -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
JDK 26 cache built under Z run under G1 -> unusable: saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime
JDK 26 cache built under Z run under Z -> heap objects used (full module graph: enabled)
JDK 27 cache built under G1 run under Serial -> heap objects used (full module graph: enabled)
JDK 27 cache built under G1 run under G1 -> heap objects used (full module graph: enabled)
JDK 27 cache built under G1 run under Z -> unusable: saved state of UseCompressedOops (1) is different from runtime (0)
JDK 27 cache built under Z run under Serial -> unusable: saved state of UseCompressedOops (0) is different from runtime (1)
JDK 27 cache built under Z run under G1 -> unusable: saved state of UseCompressedOops (0) is different from runtime (1)
JDK 27 cache built under Z run under Z -> heap objects used (full module graph: enabled)
+7
View File
@@ -0,0 +1,7 @@
# Wall-clock ms for 'java -cp app.jar AotApp' (median of 9), no AOT cache vs cache built under the same GC. INDICATIVE: shared 2-CPU sandbox.
JDK 25 G1 no cache: 57 ms with cache: 37 ms
JDK 25 Z no cache: 83 ms with cache: 81 ms
JDK 26 G1 no cache: 59 ms with cache: 35 ms
JDK 26 Z no cache: 62 ms with cache: 40 ms
JDK 27 G1 no cache: 61 ms with cache: 36 ms
JDK 27 Z no cache: 63 ms with cache: 38 ms
+34
View File
@@ -0,0 +1,34 @@
# Source that compiled on 25 and does not on 26
=== recap26/src/broken/AppletGone.java
javac (JDK 25, --release 25): exit 0
<repo>/recap26/src/broken/AppletGone.java:5: warning: [removal] Applet in java.applet has been deprecated and marked for removal
public class AppletGone extends Applet {
^
1 warning
javac (JDK 26, --release 26): exit 1
<repo>/recap26/src/broken/AppletGone.java:3: error: package java.applet does not exist
import java.applet.Applet;
^
<repo>/recap26/src/broken/AppletGone.java:5: error: cannot find symbol
public class AppletGone extends Applet {
^
symbol: class Applet
<repo>/recap26/src/broken/AppletGone.java:6: error: init() in AppletGone does not override or implement a method from a supertype
@Override public void init() { System.out.println("applet init"); }
^
3 errors
=== recap26/src/broken/ThreadStopGone.java
javac (JDK 25, --release 25): exit 0
<repo>/recap26/src/broken/ThreadStopGone.java:5: warning: [removal] stop() in Thread has been deprecated and marked for removal
t.stop();
^
1 warning
javac (JDK 26, --release 26): exit 1
<repo>/recap26/src/broken/ThreadStopGone.java:5: error: cannot find symbol
t.stop();
^
symbol: method stop()
location: variable t of type Thread
1 error
+12
View File
@@ -0,0 +1,12 @@
CHECK ok : "STRASSE".equalsFoldCase("stra\u00DFe") is true (full case folding, sharp s)
CHECK ok : equalsIgnoreCase says false for the same pair (simple folding only)
CHECK ok : "apple".compareToFoldCase("BANANA") < 0
CHECK ok : sorted with String.UNICODE_CASEFOLD_ORDER: [A, b, B, c]
CHECK ok : UUID.ofEpochMillis(..).version() == 7
CHECK ok : the first 48 bits are the timestamp: 018bcfe5-6800-...
CHECK ok : try-with-resources on Process (implements Closeable in 26)
CHECK ok : MemoryMXBean.getTotalGcCpuTime() exists and is non-negative
CHECK ok : ByteOrder is an enum: [LITTLE_ENDIAN, BIG_ENDIAN]
CHECK ok : HttpClient.Version.HTTP_3 exists and is accepted by the builder
CHECK ok : Character.UnicodeBlock.SIDETIC exists (Unicode 17)
exit code: 0
+26
View File
@@ -0,0 +1,26 @@
# JEP 517: a client that prefers HTTP/3 against a server that only speaks HTTP/1.1 over TLS (loopback, JDK's own HttpsServer)
# There is no live HTTP/3 server in this transcript: the sandbox has no UDP egress. The API surface is javap output below.
client asked for : HTTP_3
server answered : HTTP_1_1 200 hello
HTTP_3_URI_ONLY : java.net.http.HttpConnectTimeoutException: quic handshake timeout
# javap --module java.net.http -public java.net.http.HttpOption 'java.net.http.HttpOption$Http3DiscoveryMode'
Compiled from "HttpOption.java"
public interface java.net.http.HttpOption<T> {
public static final java.net.http.HttpOption<java.net.http.HttpOption$Http3DiscoveryMode> H3_DISCOVERY;
public abstract java.lang.String name();
public abstract java.lang.Class<T> type();
}
Compiled from "HttpOption.java"
public final class java.net.http.HttpOption$Http3DiscoveryMode extends java.lang.Enum<java.net.http.HttpOption$Http3DiscoveryMode> {
public static final java.net.http.HttpOption$Http3DiscoveryMode ANY;
public static final java.net.http.HttpOption$Http3DiscoveryMode ALT_SVC;
public static final java.net.http.HttpOption$Http3DiscoveryMode HTTP_3_URI_ONLY;
public static java.net.http.HttpOption$Http3DiscoveryMode[] values();
public static java.net.http.HttpOption$Http3DiscoveryMode valueOf(java.lang.String);
}
# javap ... HttpClient$Version
public static final java.net.http.HttpClient$Version HTTP_1_1;
public static final java.net.http.HttpClient$Version HTTP_2;
public static final java.net.http.HttpClient$Version HTTP_3;
+31
View File
@@ -0,0 +1,31 @@
import com.sun.management.HotSpotDiagnosticMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.stream.Collectors;
/**
* Prints which garbage collector the JVM picked when nobody asked for one, and why.
*
* <p>The "why" is the interesting part: HotSpot records the ORIGIN of every flag value.
* {@code ERGONOMIC} means the JVM chose it by looking at the machine; {@code DEFAULT} means
* it is simply the built-in value; {@code COMMAND_LINE} means somebody typed it.
*
* <p>Explained in docs/02-g1-default.md. Run by scripts/g1-default.sh.
*/
public class GcReport {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
System.out.println("java.version = " + System.getProperty("java.version"));
System.out.println("availableCpus = " + rt.availableProcessors());
System.out.println("maxHeapMB = " + rt.maxMemory() / (1024 * 1024));
System.out.println("collector beans = " + ManagementFactory.getGarbageCollectorMXBeans().stream()
.map(GarbageCollectorMXBean::getName).collect(Collectors.joining(", ")));
HotSpotDiagnosticMXBean hs = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
for (String flag : new String[] {"UseG1GC", "UseSerialGC", "UseParallelGC"}) {
var opt = hs.getVMOption(flag);
System.out.printf("%-17s = %-5s (origin %s)%n", flag, opt.getValue(), opt.getOrigin());
}
}
}
+67
View File
@@ -0,0 +1,67 @@
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* A deliberately boring allocation workload, used to ask one question: what does the switch from
* Serial to G1 cost (or save) on a machine that used to get Serial?
*
* <p>It allocates small objects as fast as it can, keeps roughly 1% of them alive in a ring buffer
* (so the collector has real work to do, not just an empty young generation), and reports wall
* time, collection count and time, the slowest single batch (a rough stand-in for the worst
* stall an application thread saw), and the process's peak resident memory from /proc.
*
* <p>Timing numbers are indicative, not a benchmark: one process, one run, a shared machine.
* The point is the SHAPE of the difference. See docs/02-g1-default.md.
*/
public class Workload {
record Item(long id, String label, byte[] payload) {}
public static void main(String[] args) throws Exception {
int batches = args.length > 0 ? Integer.parseInt(args[0]) : 400;
int perBatch = 50_000;
Item[] ring = new Item[100_000];
int ringPos = 0;
long checksum = 0;
long slowestBatchNanos = 0;
long start = System.nanoTime();
for (int b = 0; b < batches; b++) {
long t0 = System.nanoTime();
for (int i = 0; i < perBatch; i++) {
long id = (long) b * perBatch + i;
Item item = new Item(id, "item-" + id, new byte[64 + (int) (id & 63)]);
checksum += item.payload().length;
if (id % 100 == 0) {
ring[ringPos] = item;
ringPos = (ringPos + 1) % ring.length;
}
}
slowestBatchNanos = Math.max(slowestBatchNanos, System.nanoTime() - t0);
}
long elapsedMs = (System.nanoTime() - start) / 1_000_000;
long gcCount = 0, gcMs = 0;
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
gcCount += Math.max(0, gc.getCollectionCount());
gcMs += Math.max(0, gc.getCollectionTime());
}
System.out.printf("allocated objects = %,d (checksum %d)%n", (long) batches * perBatch, checksum);
System.out.printf("wall time = %d ms%n", elapsedMs);
System.out.printf("gc count / time = %d / %d ms%n", gcCount, gcMs);
System.out.printf("slowest batch = %d ms%n", slowestBatchNanos / 1_000_000);
System.out.printf("peak RSS = %d MB%n", peakRssKb() / 1024);
}
/** VmHWM is the high-water mark of the resident set, in kB. */
static long peakRssKb() throws Exception {
for (String line : Files.readAllLines(Path.of("/proc/self/status"))) {
if (line.startsWith("VmHWM:")) {
return Long.parseLong(line.replaceAll("\\D+", ""));
}
}
return -1;
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Tiny assertion helper so every demo is self-checking: a claim that stops being true turns the
* run red instead of quietly printing something different. Passing checks are echoed, so the
* transcript shows exactly what was verified.
*/
final class Check {
private Check() {}
static void that(boolean condition, String claim) {
if (!condition) {
throw new AssertionError("CHECK FAILED: " + claim);
}
System.out.println("CHECK ok : " + claim);
}
}
+45
View File
@@ -0,0 +1,45 @@
import java.io.DataInputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
/**
* How big is the TLS ClientHello this JDK sends?
*
* <p>A plain TCP server accepts the connection, reads the first TLS record header (5 bytes: type,
* version, 2-byte length) and then the record body, and reports the length. The client handshake
* never completes, and does not need to. Post-quantum key shares are large: this is the number that
* decides whether a hello still fits in one network packet. Explained in docs/04-post-quantum-tls.md.
*/
public class ClientHelloSize {
public static void main(String[] args) throws Exception {
try (ServerSocket listener = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) {
Thread client = new Thread(() -> {
try (SSLSocket s = (SSLSocket) SSLSocketFactory.getDefault()
.createSocket(InetAddress.getLoopbackAddress(), listener.getLocalPort())) {
s.setSoTimeout(1_000);
s.startHandshake(); // will time out: nobody answers
} catch (Exception expected) {
// the server side closes without replying
}
});
client.start();
try (Socket raw = listener.accept()) {
DataInputStream in = new DataInputStream(raw.getInputStream());
int type = in.readUnsignedByte();
int version = in.readUnsignedShort();
int length = in.readUnsignedShort();
byte[] body = new byte[length];
in.readFully(body);
System.out.printf("java.version = %s%n", System.getProperty("java.version"));
System.out.printf("record type = 0x%02x (0x16 = handshake)%n", type);
System.out.printf("ClientHello record = %d bytes (+5 byte record header)%n", length);
System.out.printf("fits one TCP segment = %s (1460-byte MSS on a 1500-byte MTU)%n", length + 5 <= 1460 ? "yes" : "NO");
}
client.join();
}
}
}
+6
View File
@@ -0,0 +1,6 @@
/** Does nothing for a second. Exists only so a JVM has arguments, properties and environment for JFR to record. */
public class Idle {
public static void main(String[] args) throws Exception {
Thread.sleep(1_000);
}
}
+94
View File
@@ -0,0 +1,94 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* JEP 531, Lazy Constants (third preview), against the JDK 27 API.
*
* <p>A lazy constant is a value that is computed the first time somebody asks for it and never again,
* even when many threads ask at once. The JVM can then treat it like a {@code final} field. What changed
* since JDK 26: {@code orElse(...)} and {@code isInitialized()} are gone (see broken/Lazy26.java), and
* {@code Set.ofLazy(...)} arrived next to {@code List.ofLazy} and {@code Map.ofLazy}.
* Explained in docs/06-lazy-constants.md. Compile and run with --enable-preview.
*/
public class LazyDemo {
static final AtomicInteger INITIALISATIONS = new AtomicInteger();
/** The expensive thing: pretend this reads a file and parses it. */
record Settings(String region, int poolSize) {}
static Settings loadSettings() {
INITIALISATIONS.incrementAndGet();
System.out.println(" (loading settings on " + Thread.currentThread().getName() + ")");
return new Settings("ap-south-1", 16);
}
static final LazyConstant<Settings> SETTINGS = LazyConstant.of(LazyDemo::loadSettings);
/** Identity hashes and lambda addresses change on every run; blank them so the transcript is stable. */
static String tidy(Object o) {
return o.toString().replaceAll("@[0-9a-f]+", "@...").replaceAll("/0x[0-9a-f]+", "/0x...");
}
public static void main(String[] args) throws Exception {
System.out.println("--- a lazy constant computes once, on first use");
System.out.println("toString before first get(): " + tidy(SETTINGS));
Settings first = SETTINGS.get();
Settings second = SETTINGS.get();
System.out.println("toString after first get(): " + tidy(SETTINGS));
Check.that(first == second, "get() returns the same instance every time");
Check.that(INITIALISATIONS.get() == 1, "the supplier ran exactly once");
System.out.println("--- and once even when 64 threads race for it");
var raced = LazyConstant.of(() -> {
INITIALISATIONS.incrementAndGet();
return "computed";
});
int before = INITIALISATIONS.get();
var start = new CountDownLatch(1);
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 64; i++) {
pool.submit(() -> {
start.await();
return raced.get();
});
}
start.countDown();
}
Check.that(INITIALISATIONS.get() - before == 1, "64 racing threads triggered exactly one initialisation");
System.out.println("--- List.ofLazy: each element is computed on first access");
AtomicInteger listComputations = new AtomicInteger();
List<String> squares = List.ofLazy(5, i -> {
listComputations.incrementAndGet();
return "square(" + i + ")=" + (i * i);
});
System.out.println("computed so far: " + listComputations.get());
System.out.println("squares.get(3) = " + squares.get(3));
System.out.println("squares.get(3) = " + squares.get(3) + " (second read)");
Check.that(listComputations.get() == 1, "only element 3 was ever computed");
System.out.println("--- Map.ofLazy: the keys are fixed, the values are computed on first access");
AtomicInteger mapComputations = new AtomicInteger();
Map<String, Integer> lengths = Map.ofLazy(Set.of("alpha", "beta", "gamma"), k -> {
mapComputations.incrementAndGet();
return k.length();
});
System.out.println("lengths.get(\"gamma\") = " + lengths.get("gamma"));
Check.that(mapComputations.get() == 1, "only the requested key's value was computed");
System.out.println("--- Set.ofLazy (new in 27): membership is decided by a predicate, on demand");
AtomicInteger setComputations = new AtomicInteger();
Set<Integer> primes = Set.ofLazy(Set.of(2, 3, 4, 5, 6, 7), n -> {
setComputations.incrementAndGet();
return n > 1 && java.util.stream.IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(d -> n % d == 0);
});
System.out.println("primes.contains(7) = " + primes.contains(7) + ", primes.contains(6) = " + primes.contains(6));
Check.that(primes.contains(7) && !primes.contains(6), "the lazy set answers membership using the predicate");
System.out.println("primes = " + new java.util.TreeSet<>(primes));
}
}
+98
View File
@@ -0,0 +1,98 @@
import java.nio.charset.StandardCharsets;
import java.security.BinaryEncodable;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PEM;
import java.security.PEMDecoder;
import java.security.PEMEncoder;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.EncryptedPrivateKeyInfo;
import java.util.Arrays;
import java.util.Base64;
/**
* JEP 538, PEM Encodings of Cryptographic Objects (third preview), against the JDK 27 API.
*
* <p>PEM is the "-----BEGIN ...-----" text format every key and certificate file on a Linux box is in.
* Before this API, turning a key into PEM or back meant hand-rolled Base64 and header strings.
* Note the interface name: JDK 26 called the common supertype {@code DEREncodable}; JDK 27 renamed it
* {@code BinaryEncodable}. See broken/Pem26Style.java. Explained in docs/08-pem-api.md.
* Compile and run with --enable-preview.
*/
public class PemDemo {
public static void main(String[] args) throws Exception {
KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
gen.initialize(256);
KeyPair pair = gen.generateKeyPair();
// 1. Encode: one call, no Base64 in sight.
String publicPem = PEMEncoder.of().encodeToString(pair.getPublic());
String privatePem = PEMEncoder.of().encodeToString(pair.getPrivate());
System.out.println("--- public key, encoded");
System.out.println(firstLines(publicPem, 1) + " ... " + lastLine(publicPem));
System.out.println("--- private key, encoded");
System.out.println(firstLines(privatePem, 1) + " ... " + lastLine(privatePem));
Check.that(publicPem.startsWith("-----BEGIN PUBLIC KEY-----"), "a PublicKey encodes as BEGIN PUBLIC KEY");
Check.that(privatePem.startsWith("-----BEGIN PRIVATE KEY-----"), "a PrivateKey encodes as BEGIN PRIVATE KEY (PKCS#8)");
// 2. Decode, asking for the type you expect.
PublicKey publicBack = PEMDecoder.of().decode(publicPem, PublicKey.class);
PrivateKey privateBack = PEMDecoder.of().decode(privatePem, PrivateKey.class);
Check.that(publicBack.equals(pair.getPublic()), "the decoded public key equals the original");
Check.that(privateBack.equals(pair.getPrivate()), "the decoded private key equals the original");
// 3. Decode without saying what you expect, and switch on what came back.
System.out.println("--- decode(String) returns whatever the header says it is");
for (String pem : new String[] {publicPem, privatePem}) {
BinaryEncodable decoded = PEMDecoder.of().decode(pem);
String kind = switch (decoded) {
case PublicKey k -> "PublicKey (" + k.getAlgorithm() + ")";
case PrivateKey k -> "PrivateKey (" + k.getAlgorithm() + ")";
default -> decoded.getClass().getName();
};
System.out.println(pem.lines().findFirst().orElse("") + " -> " + kind);
}
// 4. Encrypted private keys: the password goes on the encoder and the decoder.
char[] password = "correct horse".toCharArray();
String encryptedPem = PEMEncoder.of().withEncryption(password).encodeToString(pair.getPrivate());
System.out.println("--- encrypted private key");
System.out.println(firstLines(encryptedPem, 1));
Check.that(encryptedPem.startsWith("-----BEGIN ENCRYPTED PRIVATE KEY-----"), "withEncryption produces ENCRYPTED PRIVATE KEY");
BinaryEncodable withoutPassword = PEMDecoder.of().decode(encryptedPem);
System.out.println("decoded with no password: " + withoutPassword.getClass().getName());
Check.that(withoutPassword instanceof EncryptedPrivateKeyInfo, "without a password you get the still-encrypted structure back");
PrivateKey decrypted = PEMDecoder.of().withDecryption(password).decode(encryptedPem, PrivateKey.class);
Check.that(decrypted.equals(pair.getPrivate()), "with the password you get the original private key");
// 5. A PEM type the JDK does not know: it round-trips as a raw PEM object.
// The trap: PEM's content is the Base64 TEXT, not the payload. Hand it raw bytes and they are written verbatim.
byte[] payload = "hello, pem".getBytes(StandardCharsets.UTF_8);
String wrong = PEMEncoder.of().encodeToString(new PEM("ANKURM DEMO", payload));
System.out.println("--- PEM(type, byte[]) does NOT Base64-encode: the bytes go in as they are");
System.out.println(wrong.strip());
Check.that(wrong.contains("hello, pem"), "raw bytes appear verbatim between the header and footer");
String base64 = Base64.getEncoder().encodeToString(payload);
String right = PEMEncoder.of().encodeToString(new PEM("ANKURM DEMO", base64));
System.out.println("--- give it the Base64 text and it round-trips");
System.out.println(right.strip());
BinaryEncodable raw = PEMDecoder.of().decode(right);
Check.that(raw instanceof PEM p && p.type().equals("ANKURM DEMO")
&& new String(p.content(), StandardCharsets.US_ASCII).strip().equals(base64)
&& Arrays.equals(p.decode(), payload),
"type, Base64 text (content()) and decoded payload (decode()) all survive");
}
static String firstLines(String s, int n) {
return s.lines().limit(n).reduce((a, b) -> a + "\n" + b).orElse("");
}
static String lastLine(String s) {
return s.lines().reduce((a, b) -> b).orElse("");
}
}
+95
View File
@@ -0,0 +1,95 @@
/**
* JEP 532, Primitive Types in Patterns, instanceof, and switch (fifth preview).
*
* <p>Until now patterns worked on reference types only. With this preview a pattern can name a
* primitive type, and the test is "can this value be converted to that type WITHOUT losing
* information?" Explained in docs/05-primitive-patterns.md. Compile and run with --enable-preview.
*/
public class PrimitivePatterns {
record Reading(int celsius) {}
public static void main(String[] args) {
exactness();
switchOnLong();
switchOnObject();
recordPattern();
}
/** instanceof with a primitive type asks: is this an EXACT conversion? */
static void exactness() {
System.out.println("--- instanceof <primitive>: an exact-conversion test, not a cast");
int small = 100, big = 300;
boolean a = small instanceof byte;
boolean b = big instanceof byte;
System.out.println("100 instanceof byte = " + a);
System.out.println("300 instanceof byte = " + b + " (a byte holds -128..127)");
Check.that(a && !b, "100 converts to byte exactly, 300 does not");
if (small instanceof byte narrow) {
System.out.println("binding: byte narrow = " + narrow);
}
int precise = 16_777_217; // 2^24 + 1: the first int a float cannot hold
boolean f1 = 16_777_216 instanceof float;
boolean f2 = precise instanceof float;
System.out.println("16_777_216 instanceof float = " + f1);
System.out.println("16_777_217 instanceof float = " + f2 + " (float has a 24-bit significand)");
Check.that(f1 && !f2, "int to float is exact up to 2^24 and lossy after");
}
/** switch over a long, with a primitive pattern and a guard. */
static String classify(long value) {
return switch (value) {
case 0L -> "zero";
case 1L -> "one";
case long v when v < 0 -> "negative (" + v + ")";
case long v -> "positive (" + v + ")";
};
}
static void switchOnLong() {
System.out.println("--- switch on a long, with pattern labels and a guard");
for (long v : new long[] {0, 1, -7, 9_000_000_000L}) {
System.out.println("classify(" + v + ") = " + classify(v));
}
Check.that(classify(0).equals("zero") && classify(-7).startsWith("negative"), "long switch dispatches on constants, then patterns");
}
/** switch over an Object: primitive patterns match the boxed types they correspond to. */
static String describe(Object o) {
return switch (o) {
case int i when i > 1000 -> "a large int " + i;
case int i -> "an int " + i;
case long l -> "a long " + l;
case double d -> "a double " + d;
case String s -> "a String of length " + s.length();
default -> "something else: " + o;
};
}
static void switchOnObject() {
System.out.println("--- switch on an Object: an Integer matches 'case int'");
Object[] samples = {7, 5000, 7L, 2.5, "hello", 'x'};
for (Object o : samples) {
System.out.println("describe(" + o.getClass().getSimpleName() + " " + o + ") = " + describe(o));
}
Check.that(describe(5000).equals("a large int 5000"), "an Integer 5000 matches case int with a guard");
}
/** Record patterns can now narrow a component: Reading.celsius is an int, the pattern asks for a byte. */
static void recordPattern() {
System.out.println("--- record pattern with a narrowing primitive component");
Object[] readings = {new Reading(36), new Reading(4_000)};
for (Object o : readings) {
String verdict = switch (o) {
case Reading(byte c) -> "fits in a byte: " + c;
case Reading(int c) -> "needs a full int: " + c;
default -> "not a reading";
};
System.out.println(o + " -> " + verdict);
}
Check.that(readings[0] instanceof Reading(byte _) && !(readings[1] instanceof Reading(byte _)),
"Reading(byte c) matches 36 but not 4000");
}
}
+102
View File
@@ -0,0 +1,102 @@
import java.time.Duration;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Joiner;
import java.util.concurrent.StructuredTaskScope.Subtask;
/**
* JEP 533, Structured Concurrency (seventh preview), written against the JDK 27 API.
*
* <p>The headline change from JDK 26 is that {@code StructuredTaskScope} and {@code Joiner} gained a
* third type parameter, {@code R_X}: the exception {@code join()} throws. That lets a scope throw YOUR
* exception type rather than a generic {@code FailedException}. Compare broken/StructuredScope26.java,
* which is JDK 26 code that no longer compiles. Explained in docs/07-structured-concurrency.md.
*
* <p>Compile and run with --enable-preview.
*/
public class StructuredDemo {
/** The application's own failure type: what R_X lets us surface. */
static class OrderFailed extends Exception {
OrderFailed(Throwable cause) {
super("order failed: " + cause.getMessage(), cause);
}
}
static String fetchPrice() throws InterruptedException {
Thread.sleep(50);
return "price=42";
}
static String fetchStock() throws InterruptedException {
Thread.sleep(80);
return "stock=7";
}
static String failingCall() {
throw new IllegalStateException("inventory service is down");
}
public static void main(String[] args) throws Exception {
allSuccessful();
yourOwnExceptionType();
timeout();
}
/** The default joiner: all subtasks must succeed, failure surfaces as ExecutionException. */
static void allSuccessful() throws Exception {
System.out.println("--- allSuccessfulOrThrow(): results as a List, failures as ExecutionException");
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
scope.fork(StructuredDemo::fetchPrice);
scope.fork(StructuredDemo::fetchStock);
List<String> results = scope.join();
System.out.println("results = " + results);
Check.that(results.equals(List.of("price=42", "stock=7")), "join() returns the subtask results in fork order");
}
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
scope.fork(StructuredDemo::fetchPrice);
scope.fork(StructuredDemo::failingCall);
scope.join();
Check.that(false, "unreachable");
} catch (ExecutionException e) {
System.out.println("caught " + e.getClass().getName() + " with cause " + e.getCause());
Check.that(e.getCause() instanceof IllegalStateException, "a failing subtask surfaces as ExecutionException(cause)");
}
}
/** New in 27: hand the joiner a function and join() throws YOUR type. */
static void yourOwnExceptionType() throws InterruptedException {
System.out.println("--- allSuccessfulOrThrow(Function): join() throws the exception type you choose");
try (var scope = StructuredTaskScope.open(Joiner.<String, OrderFailed>allSuccessfulOrThrow(OrderFailed::new))) {
scope.fork(StructuredDemo::fetchPrice);
scope.fork(StructuredDemo::failingCall);
scope.join(); // declared: throws OrderFailed, InterruptedException
Check.that(false, "unreachable");
} catch (OrderFailed e) {
System.out.println("caught " + e.getClass().getSimpleName() + ": " + e.getMessage());
Check.that(e.getCause() instanceof IllegalStateException, "the mapped exception wraps the original failure");
}
}
/** Timeouts are now a configuration option; a Joiner decides what a timeout means. */
static void timeout() throws Exception {
System.out.println("--- withTimeout(...): what happens when the scope runs out of time");
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow(),
cfg -> cfg.withTimeout(Duration.ofMillis(100)))) {
Subtask<String> slow = scope.fork(() -> {
Thread.sleep(2_000);
return "too late";
});
scope.join();
Check.that(false, "unreachable");
} catch (Exception e) {
System.out.println("caught " + e.getClass().getName());
System.out.println(" cause: " + e.getCause());
Check.that(e.getCause() instanceof StructuredTaskScope.CancelledByTimeoutException
|| e instanceof StructuredTaskScope.CancelledByTimeoutException,
"a timeout is reported as CancelledByTimeoutException");
}
}
}
+44
View File
@@ -0,0 +1,44 @@
import java.io.InputStream;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
/**
* One end of a loopback TLS 1.3 handshake, so two different JDKs can be pointed at each other.
*
* <pre>
* java TlsPeer server &lt;portfile&gt; listen on an ephemeral port, write the port to a file, serve one handshake
* java TlsPeer client &lt;port&gt; connect and complete a handshake
* </pre>
*
* The scripts run both with -Djavax.net.debug=ssl:handshake and read the negotiated key-exchange group
* out of the debug log; no JDK API reports it directly. Explained in docs/04-post-quantum-tls.md.
*/
public class TlsPeer {
public static void main(String[] args) throws Exception {
if (args[0].equals("server")) {
SSLServerSocket server = (SSLServerSocket) SSLServerSocketFactory.getDefault()
.createServerSocket(0, 1, InetAddress.getLoopbackAddress());
Files.writeString(Path.of(args[1]), Integer.toString(server.getLocalPort()));
try (SSLSocket socket = (SSLSocket) server.accept()) {
socket.startHandshake();
socket.getOutputStream().write('!');
}
server.close();
} else {
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault()
.createSocket(InetAddress.getLoopbackAddress(), Integer.parseInt(args[1]));
socket.startHandshake();
System.out.println("handshake complete: " + socket.getSession().getProtocol()
+ " " + socket.getSession().getCipherSuite());
InputStream in = socket.getInputStream();
in.read();
socket.close();
}
}
}
+53
View File
@@ -0,0 +1,53 @@
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* JEP 537, Vector API (twelfth incubator). The same source and the same class file run on JDK 26
* (eleventh incubator, JEP 529) and JDK 27, because the public API did not change between them:
* docs/output/50-vector-api-surface.txt is the javap diff that shows it. For what the API is and
* how to use it well, see the companion guide to JEP 537 on ankurm.com.
* Run with --add-modules jdk.incubator.vector.
*/
public class VectorDemo {
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
static float scalarDot(float[] a, float[] b) {
float sum = 0;
for (int i = 0; i < a.length; i++) {
sum += a[i] * b[i];
}
return sum;
}
static float vectorDot(float[] a, float[] b) {
int i = 0;
FloatVector acc = FloatVector.zero(SPECIES);
int bound = SPECIES.loopBound(a.length);
for (; i < bound; i += SPECIES.length()) {
acc = FloatVector.fromArray(SPECIES, a, i).fma(FloatVector.fromArray(SPECIES, b, i), acc);
}
float sum = acc.reduceLanes(VectorOperators.ADD);
for (; i < a.length; i++) { // the tail the vector loop could not cover
sum += a[i] * b[i];
}
return sum;
}
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
System.out.println("preferred species = " + SPECIES + " (" + SPECIES.length() + " float lanes, machine dependent)");
int n = 1_000_003; // deliberately not a multiple of the lane count
float[] a = new float[n], b = new float[n];
for (int i = 0; i < n; i++) {
a[i] = (i % 7) * 0.5f;
b[i] = (i % 5) * 0.25f;
}
float scalar = scalarDot(a, b);
float vector = vectorDot(a, b);
System.out.println("scalar dot = " + scalar);
System.out.println("vector dot = " + vector);
// Floating-point addition is not associative, so the two sums differ in the last bits; compare with a tolerance.
Check.that(Math.abs(scalar - vector) / Math.abs(scalar) < 1e-3, "vector and scalar dot products agree to within 0.1%");
}
}
+13
View File
@@ -0,0 +1,13 @@
/**
* A primitive pattern placed after the reference pattern that already matches every Integer.
* The compiler rejects it on both 26 and 27. Driven by scripts/broken-on-27.sh.
*/
public class Dominated {
static String describe(Object o) {
return switch (o) {
case Integer boxed -> "an Integer";
case int primitive -> "an int"; // dominated: every Integer is already caught above
default -> "something else";
};
}
}
+17
View File
@@ -0,0 +1,17 @@
import java.lang.LazyConstant;
/**
* JDK 26 code for the lazy constants preview. It compiles on 26 and fails on 27:
* LazyConstant.orElse(...) and LazyConstant.isInitialized() were removed in the third preview.
* Driven by scripts/broken-on-27.sh.
*/
public class Lazy26 {
static final LazyConstant<String> GREETING = LazyConstant.of(() -> "hello");
public static void main(String[] args) {
if (!GREETING.isInitialized()) {
System.out.println("not yet: " + GREETING.orElse("<unset>"));
}
System.out.println(GREETING.get());
}
}
+26
View File
@@ -0,0 +1,26 @@
import java.security.DEREncodable;
import java.security.PEM;
import java.security.PEMDecoder;
import java.security.Provider;
import java.security.Security;
/**
* JDK 26 code for the PEM preview API. It compiles on 26 and fails on 27.
* Three things moved between the second and third preview (JEP 524 -> JEP 538):
* 1. DEREncodable was renamed BinaryEncodable
* 2. PEMDecoder.withFactory(Provider) became withFactoriesOf(Provider)
* 3. PEM was a record whose content() returned a String; it is now a class whose content() returns byte[]
* Driven by scripts/broken-on-27.sh, which commits the compiler's own messages.
*/
public class Pem26Style {
public static void main(String[] args) {
String text = "-----BEGIN ANKURM DEMO-----\naGVsbG8=\n-----END ANKURM DEMO-----\n";
DEREncodable decoded = PEMDecoder.of().decode(text); // 1
Provider provider = Security.getProviders()[0];
PEMDecoder viaProvider = PEMDecoder.of().withFactory(provider); // 2
if (decoded instanceof PEM pem) {
String base64Text = pem.content(); // 3
System.out.println(pem.type() + " / " + base64Text);
}
}
}
@@ -0,0 +1,23 @@
import java.util.List;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Joiner;
/**
* JDK 26 code for the structured concurrency preview. It compiles on 26 and fails on 27:
* StructuredTaskScope gained a third type parameter (the exception join() throws), and
* FailedException / TimeoutException no longer exist. Driven by scripts/broken-on-27.sh.
*/
public class StructuredScope26 {
public static void main(String[] args) throws Exception {
try (StructuredTaskScope<String, List<String>> scope =
StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
scope.fork(() -> "price=42");
List<String> results = scope.join();
System.out.println(results);
} catch (StructuredTaskScope.FailedException e) {
System.out.println("a subtask failed: " + e.getCause());
} catch (StructuredTaskScope.TimeoutException e) {
System.out.println("timed out");
}
}
}
+74
View File
@@ -0,0 +1,74 @@
import java.util.ArrayList;
import java.util.List;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;
import org.openjdk.jol.vm.VM;
/**
* JEP 534 seen from the inside: how many bytes does each object spend on its header, and what
* does that add up to across a million objects?
*
* <p>Uses JOL 0.17 ({@code org.openjdk.jol:jol-core}). Explained in docs/03-compact-headers.md.
* Run by scripts/object-headers.sh, once per JDK.
*/
public class HeaderDemo {
/** Two ints. The smallest object that is not empty. */
static class Point {
int x;
int y;
}
/** A more typical mix: a long, an int, a boolean and a reference. */
static class Order {
long id;
int quantity;
boolean paid;
String customer;
}
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
System.out.println(VM.current().details());
section("java.lang.Object");
System.out.println(ClassLayout.parseClass(Object.class).toPrintable());
section("Point { int x; int y; }");
System.out.println(ClassLayout.parseClass(Point.class).toPrintable());
section("Order { long id; int quantity; boolean paid; String customer; }");
System.out.println(ClassLayout.parseClass(Order.class).toPrintable());
section("java.lang.Long and java.lang.Integer (boxed values)");
System.out.println(ClassLayout.parseInstance(Long.valueOf(123_456_789L)).toPrintable());
System.out.println(ClassLayout.parseInstance(Integer.valueOf(123_456)).toPrintable());
section("int[3]");
System.out.println(ClassLayout.parseInstance(new int[3]).toPrintable());
section("What it adds up to: one million objects");
List<Integer> boxed = new ArrayList<>();
List<Long> longs = new ArrayList<>();
List<Point> points = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
boxed.add(1_000 + i); // above the Integer cache, so every one is a real object
longs.add(10_000_000_000L + i);
Point p = new Point();
p.x = i;
p.y = -i;
points.add(p);
}
System.out.printf("1,000,000 boxed Integer values in an ArrayList : %,d bytes%n",
GraphLayout.parseInstance(boxed).totalSize());
System.out.printf("1,000,000 boxed Long values in an ArrayList : %,d bytes%n",
GraphLayout.parseInstance(longs).totalSize());
System.out.printf("1,000,000 Point objects in an ArrayList : %,d bytes%n",
GraphLayout.parseInstance(points).totalSize());
}
static void section(String title) {
System.out.println();
System.out.println("=== " + title);
}
}
+14
View File
@@ -0,0 +1,14 @@
import org.openjdk.jol.info.ClassLayout;
/**
* JOL 0.17 cannot lay out a record out of the box: it asks sun.misc.Unsafe for field offsets, and
* Unsafe refuses for records. -Djol.magicFieldOffset=true switches JOL to a slower workaround.
* Run by scripts/object-headers.sh; both outcomes are committed as transcripts.
*/
public class RecordProbe {
record Coordinates(int lat, int lon) {}
public static void main(String[] args) {
System.out.println(ClassLayout.parseClass(Coordinates.class).toPrintable());
}
}
+42
View File
@@ -0,0 +1,42 @@
import java.math.BigDecimal;
import java.math.MathContext;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.time.Instant;
import java.util.Set;
import javax.crypto.spec.SecretKeySpec;
/**
* Small additions to the JDK 27 public API found by diffing javap dumps of 26 and 27
* (docs/output/51-api-diff-26-to-27.txt), each exercised once. Compile with --release 27.
*/
public class Api27 {
public static void main(String[] args) throws Exception {
System.out.printf("Math.acosh(2) = %.6f%n", Math.acosh(2));
System.out.printf("Math.asinh(1) = %.6f%n", Math.asinh(1));
System.out.printf("Math.atanh(.5) = %.6f%n", Math.atanh(0.5));
Check.that(Math.abs(Math.acosh(Math.cosh(1.5)) - 1.5) < 1e-12, "acosh inverts cosh");
String s = "na\u00efve caf\u00e9";
int utf8 = s.encodedLength(StandardCharsets.UTF_8);
System.out.println("\"" + s + "\".length() = " + s.length() + ", encodedLength(UTF_8) = " + utf8);
Check.that(utf8 == s.getBytes(StandardCharsets.UTF_8).length, "encodedLength matches getBytes().length without allocating the array");
BigDecimal cube = new BigDecimal("27").rootn(3, MathContext.DECIMAL64);
System.out.println("BigDecimal(27).rootn(3) = " + cube);
Check.that(cube.compareTo(new BigDecimal("3")) == 0, "rootn(3) of 27 is 3");
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, null);
char[] pw = "changeit".toCharArray();
ks.setEntry("aes-key", new KeyStore.SecretKeyEntry(new SecretKeySpec(new byte[16], "AES")),
new KeyStore.PasswordProtection(pw));
Instant created = ks.getCreationInstant("aes-key");
System.out.println("KeyStore.getCreationInstant(\"aes-key\") returns an Instant: " + (created != null));
Check.that(created != null && Math.abs(created.toEpochMilli() - System.currentTimeMillis()) < 60_000,
"KeyStore.getCreationInstant returns the entry's creation time as a java.time.Instant");
Set<Integer> evens = Set.ofLazy(Set.of(1, 2, 3, 4), n -> n % 2 == 0);
Check.that(evens.contains(2) && !evens.contains(3), "Set.ofLazy(Set, Predicate) exists (preview API)");
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Tiny assertion helper so every demo is self-checking: a claim that stops being true turns the
* run red instead of quietly printing something different. Passing checks are echoed, so the
* transcript shows exactly what was verified.
*/
final class Check {
private Check() {}
static void that(boolean condition, String claim) {
if (!condition) {
throw new AssertionError("CHECK FAILED: " + claim);
}
System.out.println("CHECK ok : " + claim);
}
}
+13
View File
@@ -0,0 +1,13 @@
import java.util.Locale;
/**
* A behaviour change from the JDK 27 release notes (JDK-8355522) that needs no new API to trigger, so the
* same class file can run on 26 and 27 and show the difference. Run by scripts/other-changes.sh.
*/
public class Compat {
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
System.out.println("Locale(\"iw\").language = " + Locale.forLanguageTag("iw").getLanguage()
+ " (Hebrew; old code 'iw', current code 'he')");
}
}
+7
View File
@@ -0,0 +1,7 @@
/** Starts one child process. JDK 27 removed the VFORK launch mechanism, so a script that pinned it now fails here. */
public class SpawnProbe {
public static void main(String[] args) throws Exception {
Process p = new ProcessBuilder("true").start();
System.out.println("child exited with " + p.waitFor());
}
}
+11
View File
@@ -0,0 +1,11 @@
import java.time.*;
import java.util.*;
import java.util.stream.*;
public class AotApp {
public static void main(String[] a) {
long t0 = System.nanoTime();
var m = IntStream.range(0, 2000).boxed().collect(Collectors.groupingBy(i -> i % 7, TreeMap::new, Collectors.summingInt(i -> i)));
String s = String.format("%s %s %s", m, LocalDate.of(2026, 9, 21).getDayOfWeek(), Duration.ofSeconds(90));
System.out.println(s.length() > 10 ? "ok" : "no");
}
}
+44
View File
@@ -0,0 +1,44 @@
import java.lang.management.ManagementFactory;
import java.net.http.HttpClient;
import java.nio.ByteOrder;
import java.util.List;
import java.util.UUID;
/**
* Public API that first appeared in Java 26 and needs no --enable-preview. Each call is asserted, so the
* run is the proof. The list was produced by diffing javap dumps of 25 and 26 (docs/output/50-*).
* Chapter: docs/11-recap-26.md
*/
public class Api26 {
public static void main(String[] args) throws Exception {
// String: Unicode case-folding comparisons, no more toLowerCase(Locale.ROOT) allocations
Check.that("STRASSE".equalsFoldCase("straße"), "\"STRASSE\".equalsFoldCase(\"stra\\u00DFe\") is true (full case folding, sharp s)");
Check.that(!"STRASSE".equalsIgnoreCase("straße"), "equalsIgnoreCase says false for the same pair (simple folding only)");
Check.that("apple".compareToFoldCase("BANANA") < 0, "\"apple\".compareToFoldCase(\"BANANA\") < 0");
List<String> names = new java.util.ArrayList<>(List.of("b", "A", "c", "B"));
names.sort(String.UNICODE_CASEFOLD_ORDER);
Check.that(names.get(0).equalsFoldCase("a") && names.get(2).equalsFoldCase("b"), "sorted with String.UNICODE_CASEFOLD_ORDER: " + names);
// UUID: a v7-shaped UUID for a given instant
UUID u = UUID.ofEpochMillis(1_700_000_000_000L);
Check.that(u.version() == 7, "UUID.ofEpochMillis(..).version() == 7");
Check.that(u.toString().startsWith("018bcfe5-6800"), "the first 48 bits are the timestamp: " + u.toString().substring(0, 13) + "-...");
// Process is AutoCloseable now
try (Process p = new ProcessBuilder("true").start()) {
Check.that(p.waitFor() == 0, "try-with-resources on Process (implements Closeable in 26)");
}
// MemoryMXBean: cumulative CPU time spent in GC
long gcCpu = ManagementFactory.getMemoryMXBean().getTotalGcCpuTime();
Check.that(gcCpu >= 0, "MemoryMXBean.getTotalGcCpuTime() exists and is non-negative");
// ByteOrder became an enum
Check.that(ByteOrder.BIG_ENDIAN instanceof Enum<?>, "ByteOrder is an enum: " + java.util.Arrays.toString(ByteOrder.values()));
// HTTP/3 in the client API
HttpClient c = HttpClient.newBuilder().version(HttpClient.Version.HTTP_3).build();
Check.that(c.version() == HttpClient.Version.HTTP_3, "HttpClient.Version.HTTP_3 exists and is accepted by the builder");
Check.that(Character.UnicodeBlock.SIDETIC != null, "Character.UnicodeBlock.SIDETIC exists (Unicode 17)");
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Tiny assertion helper so every demo is self-checking: a claim that stops being true turns the
* run red instead of quietly printing something different. Passing checks are echoed, so the
* transcript shows exactly what was verified.
*/
final class Check {
private Check() {}
static void that(boolean condition, String claim) {
if (!condition) {
throw new AssertionError("CHECK FAILED: " + claim);
}
System.out.println("CHECK ok : " + claim);
}
}
+33
View File
@@ -0,0 +1,33 @@
import java.lang.reflect.Field;
/**
* JEP 500 (Java 26): "Prepare to Make Final Mean Final". Deep reflection on a final field
* (Field.setAccessible(true) followed by Field.setInt) used to work silently. On 26 it works but
* warns once; a future release will deny it unless you opt in.
*
* The same file is run five ways by scripts/recap26.sh; the transcript is docs/output/70-final-field-mutation.txt.
* Chapter: docs/11-recap-26.md
*/
public class FinalFieldMutation {
static class Config {
private final int port = 8080;
int port() { return port; }
}
public static void main(String[] args) throws Exception {
Config c = new Config();
Field f = Config.class.getDeclaredField("port");
f.setAccessible(true);
try {
f.setInt(c, 9090);
} catch (IllegalAccessException e) {
System.out.println("setInt refused : " + e.getMessage());
return;
}
// The field reads 9090 but port() still says 8080: 'final int port = 8080' is a constant variable, so javac
// copied the literal 8080 into port() at compile time. Nothing you write to the field can reach that copy.
// That silent disagreement is the reason the platform wants this to stop, not a hypothetical.
System.out.println("field reads : " + f.getInt(c));
System.out.println("port() returns : " + c.port());
}
}
+53
View File
@@ -0,0 +1,53 @@
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpOption;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
/**
* JEP 517 (Java 26): HTTP/3 for the HTTP Client API. The sandbox cannot reach a real HTTP/3 server (no UDP egress),
* so this shows the half that is checkable everywhere: a client that PREFERS HTTP/3 talking to a server that does not
* speak it (the JDK's own HttpsServer, HTTP/1.1) quietly falls back, and says so in response.version().
* args: keystore path, password
* Chapter: docs/11-recap-26.md
*/
public class Http3Fallback {
public static void main(String[] args) throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
try (var in = new FileInputStream(args[0])) { ks.load(in, args[1].toCharArray()); }
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, args[1].toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
HttpsServer server = HttpsServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.setHttpsConfigurator(new HttpsConfigurator(ctx));
server.createContext("/", ex -> { byte[] b = "hello".getBytes(); ex.sendResponseHeaders(200, b.length); ex.getResponseBody().write(b); ex.close(); });
server.start();
int port = server.getAddress().getPort();
try (HttpClient client = HttpClient.newBuilder().sslContext(ctx).version(HttpClient.Version.HTTP_3).build()) {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://localhost:" + port + "/")).GET().build();
HttpResponse<String> r = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("client asked for : " + client.version());
System.out.println("server answered : " + r.version() + " " + r.statusCode() + " " + r.body());
HttpRequest strict = HttpRequest.newBuilder(URI.create("https://localhost:" + port + "/")).GET()
.setOption(HttpOption.H3_DISCOVERY, HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY).build();
try {
client.send(strict, HttpResponse.BodyHandlers.ofString());
System.out.println("HTTP_3_URI_ONLY : unexpectedly succeeded");
} catch (Exception e) {
System.out.println("HTTP_3_URI_ONLY : " + e.getClass().getName() + ": " + e.getMessage());
}
} finally {
server.stop(0);
}
}
}
+7
View File
@@ -0,0 +1,7 @@
// JEP 504 (Java 26) removed java.applet.*, javax.swing.JApplet and the AppletInitializer overload in java.beans.
// On 25 this compiles with deprecation warnings. On 26 it does not compile.
import java.applet.Applet;
public class AppletGone extends Applet {
@Override public void init() { System.out.println("applet init"); }
}
+7
View File
@@ -0,0 +1,7 @@
// Thread.stop() has thrown UnsupportedOperationException since 20. In 26 the method itself is gone.
public class ThreadStopGone {
public static void main(String[] args) {
Thread t = new Thread(() -> {});
t.stop();
}
}
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Public-API diffs between JDK 25 (LTS), 26 and 27, produced from javap dumps of every exported class.
# ./scripts/api-diff.sh -> docs/output/50-vector-api-surface.txt, 51-api-diff-26-to-27.txt, 52-api-diff-25-to-26.txt
# The dumps are 5 MB each and take a few minutes to build, so they are cached in build/apidiff/.
set -euo pipefail
source "$(dirname "$0")/env.sh"
D="$ROOT/build/apidiff"; mkdir -p "$D"
for v in 25 26 27; do
jdk="JDK$v"
[ -s "$D/api-$v.txt" ] || python3 "$ROOT/api-diff/dump_api.py" "${!jdk}" "$D/api-$v.txt"
done
{ echo "# Public API removed/added/changed between JDK 26 and 27 (javap -public of every exported class; internal packages skipped)"; echo
python3 "$ROOT/api-diff/apidiff.py" "$D/api-26.txt" "$D/api-27.txt"; } > "$OUT/51-api-diff-26-to-27.txt"
{ echo "# Public API removed/added/changed between JDK 25 and 26"; echo
python3 "$ROOT/api-diff/apidiff.py" "$D/api-25.txt" "$D/api-26.txt"; } > "$OUT/52-api-diff-25-to-26.txt"
{ echo "# jdk.incubator.vector: what changed between 26 and 27 (JEP 529 was the 11th incubation in 26, JEP 537 is the 12th in 27)"; echo
python3 "$ROOT/api-diff/apidiff.py" "$D/api-26.txt" "$D/api-27.txt" --vector-only; } > "$OUT/50-vector-api-surface.txt"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# The most useful demonstrations of a preview API are the compile errors. This compiles the same
# JDK 26 sources with the 26 and the 27 compiler and commits what javac actually said.
# ./scripts/broken-on-27.sh -> docs/output/20-broken-*.txt
set -uo pipefail
source "$(dirname "$0")/env.sh"
SRC="$ROOT/jep-tour/src"
compile() { # jdk-home release file -> prints javac's own output, then its real exit code
local out rc
out="$( cd "$SRC/broken" && "$1/bin/javac" --enable-preview --release "$2" -Xlint:-preview -d "$ROOT/build/broken-$2" "$3" 2>&1 )"; rc=$?
printf '%s\n' "$out" | grep -v -E '^(Note: |$)' || true
echo "exit=$rc"
}
for f in Pem26Style StructuredScope26 Lazy26; do
out="$OUT/20-broken-$(echo "$f" | tr 'A-Z' 'a-z').txt"
{
echo "\$ javac --enable-preview --release 26 $f.java (JDK 26)"
compile "$JDK26" 26 "$f.java"
echo
echo "\$ javac --enable-preview --release 27 $f.java (JDK 27)"
compile "$JDK27" 27 "$f.java"
} > "$out" 2>&1
echo "wrote $out"
done
{
echo "\$ javac --enable-preview --release 27 Dominated.java (JDK 27)"
compile "$JDK27" 27 Dominated.java
} > "$OUT/20-broken-dominated.txt" 2>&1
echo "wrote $OUT/20-broken-dominated.txt"
Executable
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Where the JDKs live. Override on the command line: JDK27=/path/to/jdk-27 ./scripts/run-all.sh
# Every demo that compares releases needs BOTH a JDK 26 and a JDK 27 on disk.
export JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"
export JDK26="${JDK26:-/opt/jdks/jdk26}"
export JDK27="${JDK27:-/opt/jdks/jdk27}"
# Container demos use these images (same vendor for both releases, so the only variable is the JDK version).
export IMG26="${IMG26:-amazoncorretto:26}"
export IMG27="${IMG27:-amazoncorretto:27}"
# The JVM prints "Picked up JAVA_TOOL_OPTIONS" on every launch in some sandboxes; it is noise in a transcript.
unset JAVA_TOOL_OPTIONS
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export ROOT
OUT="$ROOT/docs/output"; mkdir -p "$OUT"; export OUT
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# What does the switch cost on a machine that used to get Serial? 1 CPU, 2 GB, three runs each.
# Timing is indicative only: one shared machine, one process per run. Read the SHAPE, not the digits.
# ./scripts/g1-cost.sh -> docs/output/04-g1-cost-1cpu.txt (4000 batches, ~6-8 s per run)
# BATCHES=400 OUTNAME=05-g1-cost-1cpu-short.txt ./scripts/g1-cost.sh (a short-lived process, e.g. a CI job)
set -euo pipefail
source "$(dirname "$0")/env.sh"
BUILD="$ROOT/build/g1"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java
RUNS="${RUNS:-3}"; BATCHES="${BATCHES:-4000}"; OUTNAME="${OUTNAME:-04-g1-cost-1cpu.txt}"
{
echo "# Allocation workload, 1 CPU / 2 GB container, $BATCHES batches x 50,000 objects, $RUNS runs per configuration (indicative, not a benchmark)"
echo "# 'default' means nobody passed a collector flag."
echo
for cfg in "$IMG26|" "$IMG27|" "$IMG27|-XX:+UseSerialGC"; do
img="${cfg%%|*}"; flag="${cfg##*|}"
for i in $(seq 1 "$RUNS"); do
echo "=== $img ${flag:-default} run $i"
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$img" \
java $flag -cp /app Workload "$BATCHES" | grep -v -E '^allocated'
done
echo
done
} > "$OUT/$OUTNAME" 2>&1
cat "$OUT/$OUTNAME"
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# JEP 523 in a real container: what does the JVM pick when nobody asks for a collector?
# Runs GcReport in Docker with 1 CPU and 2 CPUs, on the JDK 26 and JDK 27 images.
# ./scripts/g1-default.sh -> docs/output/01-g1-default-1cpu-vs-2cpu.txt
# docs/output/02-g1-default-memory.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
BUILD="$ROOT/build/g1"; rm -rf "$BUILD"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java # one class file set runs on both JDKs
run() { # image label docker-args... -- java-args...
local img="$1" label="$2"; shift 2
echo "\$ docker run --rm $* $img java -cp /app GcReport"
docker run --rm "$@" -v "$BUILD:/app:ro" "$img" java -cp /app GcReport
echo
}
{
echo "# JEP 523: which collector does the JVM pick when you do not choose one?"
echo "# Same class files, same vendor (Amazon Corretto), same 2 GB memory limit. Only the JDK version and the CPU limit change."
echo
for cpus in 1 2; do
for img in "$IMG26" "$IMG27"; do
echo "=== $img --cpus=$cpus --memory=2g"
run "$img" x --cpus=$cpus --memory=2g
done
done
} > "$OUT/01-g1-default-1cpu-vs-2cpu.txt" 2>&1
{
echo "# The other half of the old rule: memory. The old heuristic wanted at least 2 CPUs AND about 1792 MB."
echo "# With 2 CPUs the CPU half of the rule is satisfied, so only the memory limit differs between these runs."
echo
for mem in 1g 2g; do
for img in "$IMG26" "$IMG27"; do
echo "=== $img --cpus=2 --memory=$mem"
run "$img" x --cpus=2 --memory=$mem
done
done
} > "$OUT/02-g1-default-memory.txt" 2>&1
{
echo "# The opt-out still works on 27: an explicit choice always wins over the JVM's."
echo
echo "=== $IMG27 --cpus=1 --memory=2g -XX:+UseSerialGC"
echo "\$ docker run --rm --cpus=1 --memory=2g $IMG27 java -XX:+UseSerialGC -cp /app GcReport"
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$IMG27" java -XX:+UseSerialGC -cp /app GcReport
} > "$OUT/03-g1-default-optout.txt" 2>&1
echo "wrote 01-, 02-, 03- transcripts to $OUT"
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Why does G1 on 1 CPU touch ~490 MB in a 0.5 s process? Two suspects, tested:
# 1. the JDK 27 change of MinHeapFreeRatio/MaxHeapFreeRatio from 40/70 to 0/100 -> restored here; no effect
# 2. G1 simply being allowed to grow toward the 512 MB default maximum heap -> capped here; RSS follows the cap
# ./scripts/g1-tuning.sh -> docs/output/06-g1-tuning-1cpu.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
BUILD="$ROOT/build/g1"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java
{
echo "# JDK 27, G1 (the default), 1 CPU / 2 GB container, 400 batches, 2 runs per configuration"
echo
echo "=== flags that differ between 26 and 27 (from -XX:+PrintFlagsFinal)"
for img in "$IMG26" "$IMG27"; do
echo "--- $img"
docker run --rm --cpus=1 --memory=2g "$img" java -XX:+PrintFlagsFinal -version 2>/dev/null \
| grep -E ' (MinHeapFreeRatio|MaxHeapFreeRatio|MaxHeapSize|ConcGCThreads|ParallelGCThreads|UseCompactObjectHeaders|G1IHOP|UseCompressedClassPointers) ' || true
done
echo
for flags in "" "-XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70" "-Xmx256m"; do
echo "=== $IMG27 flags: [${flags:-none}]"
for i in 1 2; do
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$IMG27" java $flags -cp /app Workload 400 \
| grep -E 'wall|gc count|peak'
done
done
} > "$OUT/06-g1-tuning-1cpu.txt" 2>&1
cat "$OUT/06-g1-tuning-1cpu.txt"
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Runs the five JDK 27 feature demos on a real JDK 27. Every demo asserts its own claims (see Check.java),
# so a green transcript means the sentence in the post is still true.
# ./scripts/jep-tour.sh -> docs/output/21-structured.txt 22-lazy.txt 23-primitive-patterns.txt 24-pem.txt 25-vector.txt
set -uo pipefail
source "$(dirname "$0")/env.sh"
S="$ROOT/jep-tour/src"; B="$ROOT/build/tour"; rm -rf "$B"; mkdir -p "$B"
"$JDK27/bin/javac" --enable-preview --release 27 -Xlint:-preview -d "$B" "$S/Check.java" "$S/StructuredDemo.java" "$S/LazyDemo.java" \
"$S/PrimitivePatterns.java" "$S/PemDemo.java" 2>&1 | grep -v '^Note:' || true
"$JDK27/bin/javac" --add-modules jdk.incubator.vector --release 27 -d "$B" "$S/Check.java" "$S/VectorDemo.java" 2>&1 | grep -v -E '^(Note:|WARNING)' || true
run() { local name="$1" cls="$2"; shift 2
{ echo "\$ java $* $cls (JDK 27)"; "$JDK27/bin/java" -Dstdout.encoding=UTF-8 "$@" -cp "$B" "$cls" 2>&1 | grep -v '^WARNING: Using incubator'; echo "exit=${PIPESTATUS[0]}"; } > "$OUT/$name" 2>&1; }
run 21-structured.txt StructuredDemo --enable-preview
run 22-lazy.txt LazyDemo --enable-preview
run 23-primitive-patterns.txt PrimitivePatterns --enable-preview
run 24-pem.txt PemDemo --enable-preview
run 25-vector.txt VectorDemo --add-modules jdk.incubator.vector
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# JEP 536: JFR in-process data redaction. Start a JVM that has secrets in its arguments, system
# properties and environment, record it with JFR, and print what ended up in the recording.
# ./scripts/jfr-redaction.sh -> docs/output/40-jfr-redaction.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
B="$ROOT/build/jfr"; rm -rf "$B"; mkdir -p "$B"
"$JDK27/bin/javac" --release 26 -d "$B" "$ROOT/jep-tour/src/Idle.java"
# record <label> <jdk> <FlightRecorderOptions value or empty>
record() {
local label="$1" jdk="$2" opts="${3:-}" jfr="$B/$1.jfr"
local fropt=(); [ -n "$opts" ] && fropt=("-XX:FlightRecorderOptions:$opts")
DB_PASSWORD=hunter2 "$jdk/bin/java" -Dapi.token=abc123 -Dregion=ap-south-1 "${fropt[@]}" \
-XX:StartFlightRecording:filename="$jfr" -cp "$B" Idle --password=hunter2 --user=ankur >/dev/null 2>&1
echo "=== $label"
[ -n "$opts" ] && echo " -XX:FlightRecorderOptions:$opts"
"$jdk/bin/jfr" print --events jdk.JVMInformation "$jfr" | grep -E 'jvmArguments|javaArguments' | sed 's/^ *//' | sed -E 's/(-XX:StartFlightRecording:filename=)[^ "]*/\1<file>/'
"$jdk/bin/jfr" print --events jdk.InitialSystemProperty "$jfr" | grep -A1 -E 'key = "(api.token|region)"' | grep -v -- '^--$' | sed 's/^ *//' | paste - -
"$jdk/bin/jfr" print --events jdk.InitialEnvironmentVariable "$jfr" | grep -A1 -E 'key = "DB_PASSWORD"' | grep -v -- '^--$' | sed 's/^ *//' | paste - -
echo
}
{
echo "# The process under test: java -Dapi.token=abc123 -Dregion=ap-south-1 ... Idle --password=hunter2 --user=ankur"
echo "# with DB_PASSWORD=hunter2 in the environment. Which of those values reach the recording?"
echo
record "jdk26-default" "$JDK26"
record "jdk27-default" "$JDK27"
record "jdk27-own-list-replaces-defaults" "$JDK27" "redact-argument=--user*,redact-key=region"
record "jdk27-plus-extends-defaults" "$JDK27" "redact-argument=+--user*,redact-key=+region"
record "jdk27-none-disables-redaction" "$JDK27" "redact-argument=none,redact-key=none"
} > "$OUT/40-jfr-redaction.txt" 2>&1
cat "$OUT/40-jfr-redaction.txt"
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# JEP 534: object header size on JDK 26 (12 bytes) vs JDK 27 (8 bytes), measured with JOL 0.17.
# ./scripts/object-headers.sh -> docs/output/10-headers-jdk26.txt, 11-headers-jdk27.txt,
# 12-headers-jdk27-opt-out.txt, 13-jol-record-failure.txt,
# 14-jol-record-workaround.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
LIB="$ROOT/.lib"; mkdir -p "$LIB"
JOL="$LIB/jol-core-0.17.jar"
if [ ! -f "$JOL" ]; then
curl -sfL -o "$JOL" https://repo1.maven.org/maven2/org/openjdk/jol/jol-core/0.17/jol-core-0.17.jar
fi
echo "4c98e9e61b3f189241057cb21b4331d1093e5b85 $JOL" | sha1sum -c - >/dev/null # Maven Central's own .sha1
BUILD="$ROOT/build/headers"; rm -rf "$BUILD"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -cp "$JOL" -d "$BUILD" "$ROOT"/object-headers/src/*.java
# JOL wants to attach to itself (for Instrumentation) and calls sun.misc.Unsafe, which JDK 24+ warns about.
COMMON=(-Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading --sun-misc-unsafe-memory-access=allow -cp "$BUILD:$JOL")
# The Serviceability Agent line JOL 0.17 prints on JDK 27 is JOL failing to read a flag that no longer exists
# (UseCompressedClassPointers is obsolete in 27); it is kept in the transcript because it is real.
"$JDK26/bin/java" "${COMMON[@]}" HeaderDemo > "$OUT/10-headers-jdk26.txt" 2>&1
"$JDK27/bin/java" "${COMMON[@]}" HeaderDemo > "$OUT/11-headers-jdk27.txt" 2>&1
"$JDK27/bin/java" -XX:-UseCompactObjectHeaders "${COMMON[@]}" HeaderDemo > "$OUT/12-headers-jdk27-opt-out.txt" 2>&1
# JOL 0.17 cannot lay out a record without a workaround. Capture the failure and the fix.
"$JDK27/bin/java" "${COMMON[@]}" RecordProbe > "$OUT/13-jol-record-failure.txt" 2>&1 || true
"$JDK27/bin/java" -Djol.magicFieldOffset=true "${COMMON[@]}" RecordProbe > "$OUT/14-jol-record-workaround.txt" 2>&1
# One compact table of instance sizes, extracted from the three transcripts (not retyped).
{
echo "# Instance sizes in bytes, extracted from 10-, 11- and 12- transcripts"
for f in 10-headers-jdk26 11-headers-jdk27 12-headers-jdk27-opt-out; do
echo "--- $f"
awk '/^=== /{t=$0} /^Instance size/{print t " -> " $0} /^1,000,000 /{print}' "$OUT/$f.txt"
done
} > "$OUT/15-headers-summary.txt"
cat "$OUT/15-headers-summary.txt"
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# The JDK 27 changes that are not JEPs but will still break somebody's Monday: JVM options that no
# longer exist, a launch mechanism that is gone, a locale property that is ignored, new public APIs.
# ./scripts/other-changes.sh -> docs/output/60-removed-options.txt, 61-behaviour-changes.txt, 62-new-api.txt
set -uo pipefail
source "$(dirname "$0")/env.sh"
B="$ROOT/build/other"; rm -rf "$B"; mkdir -p "$B"
SRC="$ROOT/other-changes/src"
"$JDK27/bin/javac" --release 26 -d "$B" "$SRC/Compat.java" "$SRC/SpawnProbe.java"
"$JDK27/bin/javac" --enable-preview --release 27 -d "$B" "$SRC/Check.java" "$SRC/Api27.java" 2>&1 | grep -v '^Note' || true
# first two lines of the JVM's response to a launch option, and the exit code
try() { local jdk="$1"; shift; local out rc; out="$("$jdk/bin/java" "$@" -version 2>&1 | grep -v -E '^(openjdk version|OpenJDK Runtime|OpenJDK 64-Bit Server VM \()' | head -2)"; rc=${PIPESTATUS[0]}
[ -z "$out" ] && out="(accepted silently)"; printf '%s\n' "$out" | sed 's/^/ /'; }
{
echo "# JVM options that JDK 27 removed or renamed. Each runs 'java <option> -version' on 26 and on 27."
for opt in "-noverify" "-Xverify:none" "-noclassgc" "-XX:+UseCompressedClassPointers" \
"-XX:InitiatingHeapOccupancyPercent=40" "-XX:G1IHOP=40"; do
echo; echo "=== $opt"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v:"; try "${!jdk}" $opt; done
done
echo; echo "=== -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT (JVMCI and the Graal JIT hook were removed in 27)"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v:"; try "${!jdk}" -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT; done
} > "$OUT/60-removed-options.txt" 2>&1
{
echo "# Behaviour changes that need no new API to observe"
echo; echo "=== a script that pins the VFORK process launch mechanism"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v: java -Djdk.lang.Process.launchMechanism=VFORK SpawnProbe"
"${!jdk}/bin/java" -Djdk.lang.Process.launchMechanism=VFORK -cp "$B" SpawnProbe 2>&1 | head -3 | sed 's/^/ /'; done
echo; echo "=== the legacy ISO 639 language codes property (iw, ji, in)"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v: java -Djava.locale.useOldISOCodes=true Compat"
"${!jdk}/bin/java" -Djava.locale.useOldISOCodes=true -cp "$B" Compat 2>&1 | sed 's/^/ /'; done
} > "$OUT/61-behaviour-changes.txt" 2>&1
{
echo "# New public API in 27, each exercised once (found by diffing javap dumps, see 51-api-diff-26-to-27.txt)"
"$JDK27/bin/java" --enable-preview -Dstdout.encoding=UTF-8 -cp "$B" Api27 2>&1
} > "$OUT/62-new-api.txt" 2>&1
cat "$OUT"/60-removed-options.txt "$OUT"/61-behaviour-changes.txt "$OUT"/62-new-api.txt
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# The Java 26 recap lane: what 26 changed that a 25 -> 27 upgrade meets in one go.
# ./scripts/recap26.sh -> docs/output/70-final-field-mutation.txt, 71-aot-cache-matrix.txt, 72-aot-startup.txt,
# 73-removed-in-26.txt, 74-api26.txt, 75-http3-fallback.txt
# Needs JDK 25 (LTS), 26 and 27 on disk.
set -uo pipefail
source "$(dirname "$0")/env.sh"
SRC="$ROOT/recap26/src"; B="$ROOT/build/recap26"; rm -rf "$B"; mkdir -p "$B/26" "$B/25" "$B/aot"
"$JDK26/bin/javac" -d "$B/26" "$SRC/Check.java" "$SRC/FinalFieldMutation.java" "$SRC/Api26.java" "$SRC/Http3Fallback.java"
"$JDK25/bin/javac" --release 25 -d "$B/25" "$SRC/FinalFieldMutation.java"
# ---------- JEP 500: final means final ----------
{
echo "# JEP 500: reflection writing to a final field, same class file behaviour on 25 and on 26"
echo; echo "=== JDK 25 (class compiled --release 25)"; "$JDK25/bin/java" -cp "$B/25" FinalFieldMutation 2>&1 | sed 's/^/ /'
echo; echo "=== JDK 26, defaults"; "$JDK26/bin/java" -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /'
echo; echo "=== JDK 26, --enable-final-field-mutation=ALL-UNNAMED (the opt-in the warning asks for)"
"$JDK26/bin/java" --enable-final-field-mutation=ALL-UNNAMED -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /'
echo; echo "=== JDK 26, --illegal-final-field-mutation=deny (what a future default will look like)"
"$JDK26/bin/java" --illegal-final-field-mutation=deny -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /'
echo; echo "=== JDK 26, --illegal-final-field-mutation=debug (warning plus the offending stack trace)"
"$JDK26/bin/java" --illegal-final-field-mutation=debug -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /' | head -12
for mode in "" "--enable-final-field-mutation=ALL-UNNAMED" "--illegal-final-field-mutation=deny"; do
echo; echo "=== JDK 26, JFR default settings, [${mode:-no flag}]: jfr print --events jdk.FinalFieldMutation"
rm -f "$B/ffm.jfr"
"$JDK26/bin/java" $mode -XX:StartFlightRecording:filename="$B/ffm.jfr",settings=default -cp "$B/26" FinalFieldMutation >/dev/null 2>&1
"$JDK26/bin/jfr" print --events jdk.FinalFieldMutation "$B/ffm.jfr" 2>&1 \
| grep -E 'jdk.FinalFieldMutation|declaringClass|fieldName|FinalFieldMutation.main' | sed 's/^ */ /' | sed -E 's/\{$//'
[ "${PIPESTATUS[0]}" -eq 0 ] || true
echo " events recorded: $("$JDK26/bin/jfr" summary "$B/ffm.jfr" | awk '/jdk.FinalFieldMutation /{print $2}')"
done
} > "$OUT/70-final-field-mutation.txt" 2>&1
# ---------- JEP 516: AOT object caching with any GC ----------
APP="$B/aot/AotApp"; mkdir -p "$APP"
cp "$ROOT/recap26/src/AotApp.java" "$APP/"
for v in 25 26 27; do jdk="JDK$v"; mkdir -p "$B/aot/$v/cls"
"${!jdk}/bin/javac" --release 25 -d "$B/aot/$v/cls" "$APP/AotApp.java"; (cd "$B/aot/$v/cls" && "${!jdk}/bin/jar" cf ../app.jar AotApp.class); done
mkcache() { # <jdk> <version> <gc> -> $B/aot/<version>/<gc>.aot
local j="$1" v="$2" gc="$3" d="$B/aot/$2"; rm -f "$d/$gc.aot" "$d/$gc.conf"
"$j/bin/java" -XX:+Use${gc}GC -XX:AOTMode=record -XX:AOTConfiguration="$d/$gc.conf" -cp "$d/app.jar" AotApp >/dev/null 2>&1
"$j/bin/java" -XX:+Use${gc}GC -XX:AOTMode=create -XX:AOTConfiguration="$d/$gc.conf" -XX:AOTCache="$d/$gc.aot" -cp "$d/app.jar" >/dev/null 2>&1; }
{
echo "# JEP 516: can the cache's archived heap objects be used? One row per (JDK, GC the cache was built under, GC at run time)."
echo "# 'heap objects' = the log says the archived module graph was used; 'unusable' = the JVM refused the whole cache."
for v in 25 26 27; do jdk="JDK$v"; for build in G1 Z; do mkcache "${!jdk}" "$v" "$build"
for run in Serial G1 Z; do
log="$("${!jdk}/bin/java" -XX:+Use${run}GC -XX:AOTCache="$B/aot/$v/$build.aot" -Xlog:aot=info -cp "$B/aot/$v/app.jar" AotApp 2>&1)"
if grep -q 'Unable to use AOT cache' <<<"$log"; then r="unusable: $(grep -o 'saved state of.*CDS will be disabled' <<<"$log" | head -1 | sed 's/, CDS will be disabled//')"
elif grep -q 'full module graph: enabled' <<<"$log"; then r="heap objects used (full module graph: enabled)"
else r="cache used, heap objects NOT used (full module graph: disabled)"; fi
printf 'JDK %s cache built under %-2s run under %-6s -> %s\n' "$v" "$build" "$run" "$r"
done; done; done
} > "$OUT/71-aot-cache-matrix.txt" 2>&1
median() { sort -n | awk '{a[NR]=$1} END{print a[int((NR+1)/2)]}'; }
bench() { # <jdk> <version> <gc> [cache-file]
local j="$1" v="$2" gc="$3" cache="${4:-}" i t0 t1; local -a ts=()
for i in 1 2 3 4 5 6 7 8 9; do
t0=$(date +%s%N)
if [ -n "$cache" ]; then "$j/bin/java" -XX:+Use${gc}GC -XX:AOTCache="$cache" -cp "$B/aot/$v/app.jar" AotApp >/dev/null 2>&1
else "$j/bin/java" -XX:+Use${gc}GC -Xshare:auto -XX:-AOTClassLinking -cp "$B/aot/$v/app.jar" AotApp >/dev/null 2>&1; fi
t1=$(date +%s%N); ts+=($(( (t1 - t0) / 1000000 )))
done; printf '%s\n' "${ts[@]}" | median; }
{
echo "# Wall-clock ms for 'java -cp app.jar AotApp' (median of 9), no AOT cache vs cache built under the same GC. INDICATIVE: shared 2-CPU sandbox."
for v in 25 26 27; do jdk="JDK$v"; for gc in G1 Z; do mkcache "${!jdk}" "$v" "$gc"
printf 'JDK %s %-2s no cache: %4s ms with cache: %4s ms\n' "$v" "$gc" "$(bench "${!jdk}" "$v" "$gc")" "$(bench "${!jdk}" "$v" "$gc" "$B/aot/$v/$gc.aot")"
done; done
} > "$OUT/72-aot-startup.txt" 2>&1
# committed transcripts must not carry the author's paths or per-run identity hashes
scrub() { sed -i -E "s#$ROOT#<repo>#g; s#@[0-9a-f]{6,8}#@<hash>#g" "$@"; }
scrub "$OUT/70-final-field-mutation.txt" "$OUT/71-aot-cache-matrix.txt" "$OUT/72-aot-startup.txt"
# ---------- JEP 504 and friends: things that were removed ----------
compile() { local jdk="$1" rel="$2" f="$3"; local out rc; out="$("$jdk/bin/javac" --release "$rel" -d "$B/rm" "$f" 2>&1)"; rc=$?
printf ' javac (JDK %s, --release %s): exit %s\n' "$4" "$rel" "$rc"; printf '%s\n' "$out" | grep -v '^Note:' | sed 's/^/ /'; }
mkdir -p "$B/rm"
{
echo "# Source that compiled on 25 and does not on 26"
for f in AppletGone ThreadStopGone; do echo; echo "=== recap26/src/broken/$f.java"
compile "$JDK25" 25 "$SRC/broken/$f.java" 25; compile "$JDK26" 26 "$SRC/broken/$f.java" 26; done
} > "$OUT/73-removed-in-26.txt" 2>&1
# ---------- new API in 26 ----------
{ "$JDK26/bin/java" -Dstdout.encoding=UTF-8 -cp "$B/26" Api26; echo "exit code: $?"; } > "$OUT/74-api26.txt" 2>&1
# ---------- JEP 517: HTTP/3 client, checkable half ----------
"$JDK26/bin/keytool" -genkeypair -alias demo -keyalg EC -groupname secp256r1 -dname "CN=localhost" -validity 2 -storetype PKCS12 \
-keystore "$B/ks.p12" -storepass changeit -ext san=dns:localhost >/dev/null 2>&1
{
echo "# JEP 517: a client that prefers HTTP/3 against a server that only speaks HTTP/1.1 over TLS (loopback, JDK's own HttpsServer)"
echo "# There is no live HTTP/3 server in this transcript: the sandbox has no UDP egress. The API surface is javap output below."
"$JDK26/bin/java" -cp "$B/26" Http3Fallback "$B/ks.p12" changeit 2>&1
echo; echo "# javap --module java.net.http -public java.net.http.HttpOption 'java.net.http.HttpOption\$Http3DiscoveryMode'"
"$JDK26/bin/javap" --module java.net.http -public 'java.net.http.HttpOption' 'java.net.http.HttpOption$Http3DiscoveryMode' 2>&1
echo; echo "# javap ... HttpClient\$Version"; "$JDK26/bin/javap" --module java.net.http -public 'java.net.http.HttpClient$Version' 2>&1 | grep -E 'HTTP_'
} > "$OUT/75-http3-fallback.txt" 2>&1
scrub "$OUT/73-removed-in-26.txt" "$OUT/74-api26.txt" "$OUT/75-http3-fallback.txt"
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Regenerates every file in docs/output/. Needs: JDK 25 (LTS), JDK 26 and JDK 27 on disk, Docker with the
# amazoncorretto:26 and :27 images, and python3. Takes roughly 15-20 minutes, most of it the G1 cost runs.
#
# Non-deterministic outputs (timings, RSS, GC counts, the machine's preferred SIMD species) will differ on your
# machine; the shape should not. Everything else is byte-stable between runs.
set -uo pipefail
cd "$(dirname "$0")"
./g1-default.sh
./g1-cost.sh
BATCHES=400 OUTNAME=05-g1-cost-1cpu-short.txt ./g1-cost.sh
./g1-tuning.sh
./object-headers.sh
./jep-tour.sh
./broken-on-27.sh
./tls-pq.sh
./jfr-redaction.sh
./other-changes.sh
./recap26.sh
./api-diff.sh
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# JEP 527: post-quantum hybrid key exchange in the JDK's default TLS 1.3 stack.
# ./scripts/tls-pq.sh -> docs/output/30-tls-groups-matrix.txt, 31-tls-clienthello-size.txt, 32-tls-opt-out.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
B="$ROOT/build/tls"; rm -rf "$B"; mkdir -p "$B"
"$JDK27/bin/javac" --release 26 -d "$B" "$ROOT"/jep-tour/src/TlsPeer.java "$ROOT"/jep-tour/src/ClientHelloSize.java
# A throwaway self-signed certificate for the loopback server; the same store is the client's trust store.
"$JDK27/bin/keytool" -genkeypair -alias demo -keyalg EC -groupname secp256r1 -dname "CN=localhost" -validity 2 \
-storetype PKCS12 -keystore "$B/ks.p12" -storepass changeit -ext san=dns:localhost >/dev/null 2>&1
STORE=(-Djavax.net.ssl.keyStore="$B/ks.p12" -Djavax.net.ssl.keyStorePassword=changeit
-Djavax.net.ssl.trustStore="$B/ks.p12" -Djavax.net.ssl.trustStorePassword=changeit)
DEBUG=(-Djavax.net.debug=ssl:handshake)
# handshake <server-jdk> <client-jdk> [extra client flag]
handshake() {
local sj="$1" cj="$2" cflag="${3:-}"; local pf="$B/port"; rm -f "$pf"
"$sj/bin/java" "${STORE[@]}" "${DEBUG[@]}" -cp "$B" TlsPeer server "$pf" > "$B/server.log" 2>&1 &
local spid=$!
for _ in $(seq 1 50); do [ -s "$pf" ] && break; sleep 0.1; done
"$cj/bin/java" $cflag "${STORE[@]}" "${DEBUG[@]}" -cp "$B" TlsPeer client "$(cat "$pf")" > "$B/client.log" 2>&1
wait "$spid"
echo "client offered : $(grep -m1 '"named groups"' "$B/client.log" | sed 's/^ *//')"
echo "server selected: $(awk '/"ServerHello"/{f=1} f&&/"named group"/{print $NF; exit}' "$B/client.log")"
grep -m1 'handshake complete' "$B/client.log"
}
{
echo "# Who talks to whom: the key-exchange group each pairing actually negotiates (TLS 1.3, loopback)"
for pair in "26 26" "27 27" "27 26" "26 27"; do
set -- $pair
cj="JDK$1"; sj="JDK$2"
echo; echo "=== client JDK $1 -> server JDK $2"
handshake "${!sj}" "${!cj}"
done
} > "$OUT/30-tls-groups-matrix.txt" 2>&1
{
echo "# Size of the first TLS record (the ClientHello) each JDK sends"
for v in 26 27; do
jdk="JDK$v"; echo; echo "=== JDK $v"
"${!jdk}/bin/java" -cp "$B" ClientHelloSize
done
echo; echo "=== JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 (post-quantum group removed)"
"$JDK27/bin/java" -Djdk.tls.namedGroups=x25519,secp256r1 -cp "$B" ClientHelloSize
} > "$OUT/31-tls-clienthello-size.txt" 2>&1
{
echo "# Opting out on JDK 27: a client that does not offer the hybrid group"
echo; echo "=== client JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 -> server JDK 27"
handshake "$JDK27" "$JDK27" "-Djdk.tls.namedGroups=x25519,secp256r1"
} > "$OUT/32-tls-opt-out.txt" 2>&1
cat "$OUT/30-tls-groups-matrix.txt" "$OUT/31-tls-clienthello-size.txt" "$OUT/32-tls-opt-out.txt"