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

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
This commit is contained in:
2026-09-21 15:08:50 +00:00
committed by Claude
co-authored by Claude Sonnet 5
commit f59c1de96d
152 changed files with 5049 additions and 0 deletions
+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);
}
}
+3
View File
@@ -0,0 +1,3 @@
void main() {
IO.println("compact source file: no class, no static, no String[] args");
}
+27
View File
@@ -0,0 +1,27 @@
/**
* Is finalization "removed by default" in Java 25? JEP 421 deprecated it for removal in 18 and added
* --finalization=disabled to test life without it. This allocates an object with a finalize() method, drops it,
* asks for a GC, and reports whether the finalizer ran. Run by scripts/lanes.sh with and without the flag.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class FinalizerDefault {
static volatile boolean finalized;
@SuppressWarnings({"removal", "deprecation"})
static class Resource {
@Override
protected void finalize() {
finalized = true;
}
}
public static void main(String[] args) throws Exception {
new Resource();
for (int i = 0; i < 20 && !finalized; i++) {
System.gc();
Thread.sleep(50);
}
System.out.println("java.version = " + System.getProperty("java.version"));
System.out.println("finalizer ran: " + finalized);
}
}
+101
View File
@@ -0,0 +1,101 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
/**
* The four Java 21 features from ankurm.com/java-21-to-25-lts-features (records, pattern-matching switch,
* virtual threads, sequenced collections), exactly as the article shows them, compiled once with
* --release 21 and then run unchanged on JDK 21, 25, 26 and 27. Scoped values and structured concurrency are
* separate files because they are NOT final in 21. Chapter: docs/14-lanes-21-to-25.md
*/
public class HubExamples {
// 1. Records ------------------------------------------------------------------------------
public record Customer(Long id, String email, String country) {
public Customer {
Objects.requireNonNull(email, "email must not be null");
if (!email.contains("@")) throw new IllegalArgumentException("Invalid email");
}
}
// 2. Pattern matching for switch ----------------------------------------------------------
sealed interface Shape permits Circle, Square, Triangle {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}
record Triangle(double base, double height) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Square(double side) -> side * side;
case Triangle(double b, double h) -> 0.5 * b * h;
};
}
static String classify(Shape s) {
return switch (s) {
case Circle c when c.r() > 100 -> "large circle";
case Circle c -> "small circle";
case Square sq -> "square";
case Triangle t -> "triangle";
};
}
public static void main(String[] args) throws Exception {
System.out.println("java.version = " + System.getProperty("java.version"));
Customer c = new Customer(1L, "[email protected]", "IN");
Check.that(c.email().equals("[email protected]"), "record accessor: c.email()");
Check.that(c.toString().equals("Customer[id=1, [email protected], country=IN]"), "record toString: " + c);
try {
new Customer(2L, "not-an-email", "IN");
Check.that(false, "compact constructor rejects an email without @");
} catch (IllegalArgumentException expected) {
Check.that(true, "compact constructor rejects an email without @: " + expected.getMessage());
}
Check.that(Math.abs(area(new Square(3)) - 9.0) < 1e-9, "area(Square(3)) == 9.0");
Check.that(Math.abs(area(new Triangle(4, 5)) - 10.0) < 1e-9, "area(Triangle(4, 5)) == 10.0");
Check.that(classify(new Circle(101)).equals("large circle"), "guard pattern: Circle(101) is a large circle");
Check.that(classify(new Circle(1)).equals("small circle"), "guard pattern: Circle(1) is a small circle");
// 3. Virtual threads: 10,000 tasks that each block for 100 ms. Serial would take 1,000 s.
AtomicInteger done = new AtomicInteger();
long t0 = System.nanoTime();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i ->
executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100));
return done.incrementAndGet();
}));
}
long ms = (System.nanoTime() - t0) / 1_000_000;
Check.that(done.get() == 10_000, "all 10,000 virtual-thread tasks completed before close() returned");
Check.that(ms < 30_000, "and it took seconds, not the 1,000 s a serial loop needs (exact time is machine dependent, not printed)");
Thread vt = Thread.ofVirtual().name("my-vthread").start(() -> { });
vt.join();
Check.that(vt.isVirtual() && vt.getName().equals("my-vthread"), "Thread.ofVirtual().name(..).start(..) gives a virtual thread");
// 4. Sequenced collections
List<String> names = new ArrayList<>(List.of("alex", "brian", "chris"));
String first = names.getFirst(); // alex
String last = names.getLast(); // chris
List<String> backwards = names.reversed(); // [chris, brian, alex]
String snapshot = backwards.toString(); // written down before the list changes
names.add("dana"); // reversed() is a view, so it sees this
LinkedHashSet<Integer> nums = new LinkedHashSet<>(List.of(3, 1, 4, 1, 5));
int firstNum = nums.getFirst(); // 3
int lastNum = nums.getLast(); // 5
Check.that(first.equals("alex") && last.equals("chris"), "List.getFirst() / getLast()");
Check.that(snapshot.equals("[chris, brian, alex]"), "List.reversed(): " + snapshot);
Check.that(backwards.getFirst().equals("dana"), "reversed() is a view, not a copy: it sees the element added afterwards");
Check.that(firstNum == 3 && lastNum == 5, "LinkedHashSet.getFirst() == 3, getLast() == 5");
}
}
+13
View File
@@ -0,0 +1,13 @@
/**
* "JNI warnings on stderr by default": JEP 472 (Java 24). Loading a native library is a restricted operation.
* The library is the JDK's own libnet, so no compiler or extra file is needed. Run with and without
* --enable-native-access=ALL-UNNAMED by scripts/lanes.sh.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class JniWarning {
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
System.loadLibrary("net");
System.out.println("System.loadLibrary(\"net\") returned");
}
}
+13
View File
@@ -0,0 +1,13 @@
import lombok.Getter;
/**
* The smallest class that needs Lombok's annotation processor: without the processor there is no getName().
* Compiled by scripts/upgrade.sh with four Lombok versions on JDK 25, 26 and 27. Explained in docs/15-build-files.md.
*/
public class LombokProbe {
@Getter private final String name = "ankur";
public static void main(String[] args) {
System.out.println("getName() = " + new LombokProbe().getName());
}
}
+15
View File
@@ -0,0 +1,15 @@
import org.mockito.Mockito;
/**
* Creates one mock and stubs one call: the smallest thing that makes Mockito ask ByteBuddy to read and write class files.
* Run by scripts/upgrade.sh against two Mockito versions on JDK 21 and 25. Explained in docs/15-build-files.md.
*/
public class MockitoProbe {
interface Greeter { String greet(String name); }
public static void main(String[] args) {
Greeter g = Mockito.mock(Greeter.class);
Mockito.when(g.greet("x")).thenReturn("hi");
System.out.println("mock says " + g.greet("x"));
}
}
+29
View File
@@ -0,0 +1,29 @@
import java.util.ArrayList;
import java.util.List;
/**
* Virtual threads and synchronized: until JEP 491 (Java 24) a virtual thread that blocked inside a synchronized
* block kept its carrier thread (was "pinned"). With ONE carrier, four virtual threads that each sleep 200 ms inside
* synchronized on their own lock take about 800 ms if pinned (one at a time) and about 200 ms if not (all at once).
* Run by scripts/lanes.sh with -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=1
* Chapter: docs/14-lanes-21-to-25.md
*/
public class PinningDemo {
public static void main(String[] args) throws Exception {
System.out.println("java.version = " + System.getProperty("java.version"));
List<Thread> threads = new ArrayList<>();
long t0 = System.nanoTime();
for (int i = 0; i < 4; i++) {
Object lock = new Object();
threads.add(Thread.ofVirtual().start(() -> {
synchronized (lock) {
try { Thread.sleep(200); } catch (InterruptedException ignored) { }
}
}));
}
for (Thread t : threads) t.join();
long ms = (System.nanoTime() - t0) / 1_000_000;
System.out.println("4 virtual threads, 1 carrier, sleep(200) inside synchronized: "
+ (ms >= 700 ? "SERIALISED (pinned): about 4 x 200 ms" : "OVERLAPPED (not pinned): about 200 ms"));
}
}
+10
View File
@@ -0,0 +1,10 @@
/**
* Uses one API that exists on Java 26 and later (String.equalsFoldCase) and not on Java 25.
* Compiled by a newer javac with --release 25 (rejected) and with -source 25 -target 25 (accepted, then it
* fails when it meets a Java 25 runtime). Explained in docs/15-build-files.md.
*/
public class ReleaseFlag {
public static void main(String[] args) {
System.out.println("ABC".equalsFoldCase("abc"));
}
}
+28
View File
@@ -0,0 +1,28 @@
import java.util.NoSuchElementException;
/**
* Scoped values (JEP 506): final in Java 25, a preview API in 21. The code is the example from
* ankurm.com/java-21-to-25-lts-features. Compiles WITHOUT --enable-preview on 25, 26 and 27; on 21 javac refuses.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class ScopedValueDemo {
static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
ScopedValue.where(CURRENT_USER, "ankur").run(() -> {
Check.that(CURRENT_USER.get().equals("ankur"), "inside the scope: CURRENT_USER.get() == ankur");
callDeepMethod();
});
try {
CURRENT_USER.get();
Check.that(false, "outside the scope get() should throw");
} catch (NoSuchElementException expected) {
Check.that(true, "outside the scope: CURRENT_USER.get() throws NoSuchElementException");
}
}
static void callDeepMethod() {
Check.that(CURRENT_USER.get().equals("ankur"), "a method that was never passed the value still sees it");
}
}
+17
View File
@@ -0,0 +1,17 @@
/**
* "System.setSecurityManager() now throws UnsupportedOperationException": JEP 486 (Java 24) permanently disabled
* the Security Manager. This calls it with a non-null manager and prints exactly what happens.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class SecurityManagerGone {
@SuppressWarnings({"removal", "deprecation"})
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
try {
System.setSecurityManager(new SecurityManager());
System.out.println("setSecurityManager: accepted");
} catch (UnsupportedOperationException e) {
System.out.println("setSecurityManager: UnsupportedOperationException: " + e.getMessage());
}
}
}
+37
View File
@@ -0,0 +1,37 @@
import java.util.concurrent.StructuredTaskScope;
/**
* The structured concurrency example from ankurm.com/java-21-to-25-lts-features. JEP 505 in Java 25 is the FIFTH
* PREVIEW, not a final feature: without --enable-preview javac refuses, on 25, 26 and 27 alike.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class StructuredHub {
record Dashboard(String user, String order) {}
static String fetchUser(int id) throws InterruptedException { Thread.sleep(20); return "user-" + id; }
static String fetchOrder(int id) throws InterruptedException { Thread.sleep(30); return "order-for-" + id; }
static Dashboard fanOut(int userId) throws Exception {
try (var scope = StructuredTaskScope.open()) {
var user = scope.fork(() -> fetchUser(userId));
var order = scope.fork(() -> fetchOrder(userId));
scope.join();
return new Dashboard(user.get(), order.get());
}
}
static String firstSuccess() throws Exception {
try (var scope = StructuredTaskScope.open(StructuredTaskScope.Joiner.<String>anySuccessfulResultOrThrow())) {
scope.fork(() -> { Thread.sleep(200); return "slow"; });
scope.fork(() -> "fast");
return scope.join();
}
}
public static void main(String[] args) throws Exception {
System.out.println("java.version = " + System.getProperty("java.version"));
Dashboard d = fanOut(7);
Check.that(d.equals(new Dashboard("user-7", "order-for-7")), "fan-out: both results joined: " + d);
Check.that(firstSuccess().equals("fast"), "anySuccessfulResultOrThrow(): the first result wins and the slow subtask is cancelled");
}
}
+21
View File
@@ -0,0 +1,21 @@
import java.util.concurrent.StructuredTaskScope;
/**
* The same "first result wins" example as StructuredHub, with the factory method under its Java 26 / 27 name:
* anySuccessfulResultOrThrow() (25) became anySuccessfulOrThrow() (26 and 27). Still a preview API.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class StructuredHubNew {
static String firstSuccess() throws Exception {
try (var scope = StructuredTaskScope.open(StructuredTaskScope.Joiner.<String>anySuccessfulOrThrow())) {
scope.fork(() -> { Thread.sleep(200); return "slow"; });
scope.fork(() -> "fast");
return scope.join();
}
}
public static void main(String[] args) throws Exception {
System.out.println("java.version = " + System.getProperty("java.version"));
Check.that(firstSuccess().equals("fast"), "anySuccessfulOrThrow(): the first result wins");
}
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Thread.stop() on 25: still compiles (with a deprecation-for-removal warning) but has thrown
* UnsupportedOperationException since Java 20. It is REMOVED from the API in Java 26 (see recap26/src/broken/ThreadStopGone.java,
* docs/output/73-removed-in-26.txt). Thread.suspend() and resume() are checked the same way.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class ThreadStopRuntime {
@SuppressWarnings({"removal", "deprecation"})
public static void main(String[] args) throws Exception {
System.out.println("java.version = " + System.getProperty("java.version"));
Thread t = new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException ignored) { }
});
t.start();
try {
t.stop();
System.out.println("Thread.stop(): accepted");
} catch (UnsupportedOperationException e) {
System.out.println("Thread.stop(): UnsupportedOperationException");
}
t.interrupt();
t.join();
}
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Compile-only probe: do Thread.suspend() and Thread.resume() still exist? Compiled by scripts/lanes.sh with each JDK's own javac.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class ThreadSuspendGone {
@SuppressWarnings({"removal", "deprecation"})
static void probe(Thread t) {
t.suspend();
t.resume();
}
}
+21
View File
@@ -0,0 +1,21 @@
/**
* "Unnamed patterns and unnamed classes are also final in 25" is only half true. Unnamed variables and patterns (the
* underscore) were finalised in Java 22 (JEP 456). What became final in 25 is compact source files and instance main
* methods (JEP 512), the successor to "unnamed classes". This file checks the underscore half.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class UnnamedVariables {
record Point(int x, int y) {}
public static void main(String[] args) {
Object o = new Point(3, 4);
if (o instanceof Point(int x, _)) {
System.out.println("matched a Point, x = " + x + ", y ignored with _");
}
try {
Integer.parseInt("nope");
} catch (NumberFormatException _) {
System.out.println("caught NumberFormatException without naming it");
}
}
}
+21
View File
@@ -0,0 +1,21 @@
import java.lang.reflect.Field;
import sun.misc.Unsafe;
/**
* "sun.misc.Unsafe memory methods deprecated for removal": JEP 471 (23) deprecated them, JEP 498 (24) makes the first
* call print a warning at run time. This allocates and frees 8 bytes off-heap and shows what the JVM says.
* Chapter: docs/14-lanes-21-to-25.md
*/
public class UnsafeWarning {
@SuppressWarnings("removal")
public static void main(String[] args) throws Exception {
System.out.println("java.version = " + System.getProperty("java.version"));
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe u = (Unsafe) f.get(null);
long addr = u.allocateMemory(8);
u.putLong(addr, 42L);
System.out.println("read back: " + u.getLong(addr));
u.freeMemory(addr);
}
}