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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
void main() {
|
||||
IO.println("compact source file: no class, no static, no String[] args");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user