A tour of the Java language and platform features delivered between the Java 21 and Java 25 LTS releases. Every feature has:
- a small but realistic example (fees, payments, product pages, request context),
not a
Foo/Barsnippet; - tests that act as executable documentation, which prove the claims (for example, that 10,000 blocking tasks finish in about the time of one);
- a note on when to use it in a typical Spring/backend codebase.
| Feature | JEP | Final in | Example | Test |
|---|---|---|---|---|
Record patterns, pattern matching for switch, sealed types |
440, 441, 409 | 21 | FeeCalculator, Result |
test |
| Virtual threads | 444 | 21 | BlockingWorkload |
test |
| Sequenced collections | 431 | 21 | RecentlyViewed |
test |
Unnamed variables and patterns (_) |
456 | 22 | FeeCalculator, Result |
test |
| Foreign Function & Memory API | 454 | 22 | NativeLibC |
test |
Markdown documentation comments (///) |
467 | 23 | used across the whole codebase | — |
| Stream gatherers | 485 | 24 | StreamGatherers |
test |
| Class-File API | 484 | 24 | BytecodeInspector |
test |
Virtual threads no longer pinned by synchronized |
491 | 24 | BlockingWorkload |
test |
| Scoped values | 506 | 25 | RequestContext |
test |
| Flexible constructor bodies | 513 | 25 | Rectangle |
test |
| Module import declarations | 511 | 25 | WordFrequency |
test |
Compact source files and instance main |
512 | 25 | scripts/ |
run in CI |
| Key Derivation Function API (HKDF) | 510 | 25 | KeyDerivation |
test (RFC 5869 vector) |
| Structured concurrency | 505 | preview | ProductPageService |
test |
Business rules become a single exhaustive switch. If someone adds a new payment
method, every switch that does not handle it stops compiling, which no
instanceof chain or visitor can guarantee as cheaply.
return switch (payment) {
case Payment(Card(var network, var installments), var amount) when installments > 1 ->
percent(amount, network.equals("AMEX") ? "4.5" : "3.5")
.add(new BigDecimal("0.10").multiply(BigDecimal.valueOf(installments)));
case Payment(Card(var network, _), var amount) -> percent(amount, network.equals("AMEX") ? "3.0" : "2.0");
case Payment(BankTransfer _, _) -> BANK_TRANSFER_FEE;
case Payment(DigitalWallet(var provider), _) when provider.equals("PARTNER_WALLET") -> BigDecimal.ZERO;
case Payment(DigitalWallet _, var amount) -> percent(amount, "1.5");
}; // no default: PaymentMethod is sealedThese three work together: cheap threads that may block, a safe way to fan out and cancel, and immutable context that child tasks inherit.
try (var scope = StructuredTaskScope.open()) {
Subtask<Product> product = scope.fork(() -> backends.product(id));
Subtask<Integer> stock = scope.fork(() -> backends.stock(id));
Subtask<List<String>> reviews = scope.fork(() -> backends.reviews(id));
scope.join(); // first failure cancels the other calls and is rethrown
return new ProductPage(product.get(), stock.get(), reviews.get());
}The tests check the guarantees: the calls run in parallel, a failing call interrupts its slower siblings, and a deadline applies to the whole operation.
items.stream().gather(Gatherers.windowFixed(500)) // batches for bulk inserts
sales.stream().gather(Gatherers.windowSliding(7)) // moving averages
movements.stream().gather(Gatherers.scan(() -> opening, BigDecimal::add)) // running balance
ids.stream().gather(Gatherers.mapConcurrent(10, client::fetch)) // bounded parallel I/O, order keptVirtualThreadsBenchmark
measures the time to complete N tasks that each block for 10 ms, which is roughly
a fast DB query or HTTP call.
| Strategy | 1,000 tasks | 10,000 tasks |
|---|---|---|
| Fixed pool of 200 platform threads | 109 ms | 571 ms |
| One platform thread per task | 228 ms | 3,424 ms |
| One virtual thread per task | 16 ms | 87 ms |
Indicative numbers from a laptop (Intel i7-1165G7, 8 logical cores, WSL2, JDK 25.0.4), 1 fork × 3 measurement iterations, so the error margins are wide. The theoretical floor is 10 ms (every task in parallel). What matters is the trend: the bounded pool queues work (at least N / 200 × 10 ms), and thousands of OS threads are expensive to create and schedule, while virtual threads stay close to the floor.
./mvnw -P benchmarks package -DskipTests
java --enable-preview -jar target/benchmarks.jarRequires JDK 25. Structured concurrency is a preview API, so the build passes
--enable-preview.
./mvnw verify # compile + all tests
java scripts/Hello.java # compact source file, no compilation step
java scripts/TopWords.java README.md 5| Need | Reach for |
|---|---|
| Many concurrent blocking calls (JDBC, REST clients) | Virtual threads (spring.threads.virtual.enabled=true in Spring Boot 3.2+) |
| Call several services in parallel and fail fast | Structured concurrency (preview), or CompletableFuture until it is final |
| Request id / tenant / user propagated through the call chain | Scoped values instead of ThreadLocal |
| Closed domain hierarchies (payment methods, events, commands) | Sealed interfaces + records + exhaustive switch |
| Batching, windows, running totals, bounded parallel map in streams | Gatherers |
| Deriving per-purpose keys from one secret | KDF API (HKDF) |
| Calling a native library | FFM API instead of JNI |