-
Notifications
You must be signed in to change notification settings - Fork 0
Java 19
Release Date: September 20, 2022 | Type: Standard (non-LTS) | Status:
⚠️ End of Life
Java 19 previews the most significant concurrency change in Java's history: Virtual Threads (Project Loom). It also previews Record Patterns and Structured Concurrency — three features that together redefine how Java handles concurrent I/O-bound workloads.
flowchart TD
A["Platform Threads\n1:1 mapped to OS threads\nExpensive: ~1MB stack each\nThousands = resource exhaustion"] -->|Project Loom| B["Virtual Threads\nM:N mapped to OS carrier threads\nCheap: ~few KB each\nMillions possible"]
What: Lightweight threads managed by the JVM (not the OS), enabling massive concurrency for I/O-bound code without the complexity of reactive/async programming.
// Traditional platform thread — expensive, OS-managed
Thread.ofPlatform().start(() -> {
System.out.println("Platform thread: " + Thread.currentThread());
});
// Virtual thread — cheap, JVM-managed (Preview in 19, standard in 21)
Thread.ofVirtual().start(() -> {
System.out.println("Virtual thread: " + Thread.currentThread());
});
// The killer use case: one virtual thread per request/task, written as
// simple BLOCKING code — no callbacks, no reactive operators
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) {
int taskId = i;
executor.submit(() -> {
// Blocking I/O call — perfectly fine on a virtual thread!
String result = callSlowExternalApi(taskId);
System.out.println(result);
});
}
} // executor.close() waits for all tasks to completesequenceDiagram
participant App
participant VT as Virtual Thread
participant CT as Carrier (OS) Thread
participant IO as Blocking I/O
App->>VT: submit(task)
VT->>CT: mounted on carrier thread
VT->>IO: blocking call (e.g., HTTP request)
Note over VT,CT: VT unmounts from carrier —<br/>carrier thread is FREED to run other virtual threads
IO-->>VT: response ready
VT->>CT: remounts on any available carrier
VT-->>App: task complete
100,000 platform threads would crash most systems. 100,000 virtual threads run comfortably because they don't each consume a full OS thread — they're multiplexed onto a small pool of "carrier" platform threads, and unmount from the carrier whenever they block on I/O.
What: Destructure record values directly in instanceof and (later) switch — extract fields without calling accessor methods manually.
record Point(int x, int y) {}
record Line(Point start, Point end) {}
// Before: manual accessor calls
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
}
// Record patterns (Preview) — destructure directly
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}
// Nested destructuring — this is where it really shines
if (shape instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
double length = Math.hypot(x2 - x1, y2 - y1);
System.out.println("Line length: " + length);
}flowchart LR
A["Line(Point(x1,y1), Point(x2,y2))"] -->|record pattern| B["Destructures in\none expression"]
B --> C["x1, y1, x2, y2\ndirectly usable"]
What: Treat a group of related concurrent subtasks as a single unit of work — if one fails, the others are cancelled automatically. No more "fire and forget" threads that leak or outlive their purpose.
// Before: manually coordinating two parallel calls with ExecutorService/Future
// is error-prone — a failure in one doesn't cancel the other
// Structured Concurrency (Incubator)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<User> userFuture = scope.fork(() -> fetchUser(userId));
Future<Order> orderFuture = scope.fork(() -> fetchOrder(orderId));
scope.join(); // wait for both
scope.throwIfFailed(); // propagate failure from either
User user = userFuture.resultNow();
Order order = orderFuture.resultNow();
return new Response(user, order);
}
// If fetchOrder() throws, fetchUser() is automatically cancelled —
// no orphaned threads, no resource leaksflowchart TD
A[Parent scope opens] --> B[fork: fetchUser]
A --> C[fork: fetchOrder]
B --> D{join}
C --> D
D -->|Any task fails| E["Cancel all sibling tasks\nautomatically"]
D -->|All succeed| F[Continue with results]
static String classify(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> "negative int"; // guarded pattern
case Integer i -> "non-negative int: " + i;
case String s when s.isBlank() -> "blank string";
case String s -> "string: " + s;
case null -> "it's null";
default -> "something else";
};
}✅ Start experimenting with virtual threads in side projects — for I/O-heavy services (REST APIs calling databases/other APIs), this eventually replaces the need for reactive frameworks in many cases
✅ Use structured concurrency for any "fan-out, fan-in" pattern where sibling tasks should live and die together
❌ Don't use virtual threads for CPU-bound work — they only help when threads are blocked on I/O; a CPU-bound virtual thread just occupies its carrier thread the same as a platform thread would
❌ Don't rely on preview APIs (--enable-preview) in production Java 19 code — the API shapes changed before standardization in 21