What changed in Java 24

Stream gatherers, and virtual threads stop pinning

Two changes that matter in production. Stream gatherers finally made the Stream API extensible, and JEP 491 removed the synchronized-block pinning that was the main practical caveat when adopting virtual threads.

Released 2025-03 · 3 features · 8 questions · 1 scenarios

Features

Stream gatherers

standardJEP 485

A general extension point for intermediate stream operations, so you can write windowing, folding and custom stateful operations that compose like built-ins.

The problem it solved

The set of intermediate operations was fixed. Anything not provided — sliding windows, fixed batches, take-while-with-state — meant leaving the stream, collecting to a list, and looping.

How you did it before

Collect to a list, loop over it with index arithmetic, then build a new stream. The pipeline was broken in the middle purely because the operation you needed did not exist.

Needs JDK 24 — this site builds on 21, so this sample is not verified
// Requires JDK 24 — not compiled on this build.
var windows = Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowFixed(2))
    .toList();
System.out.println(windows);
Output
[[1, 2], [3, 4], [5]]

Journey: Preview in 22 (JEP 461) and 23, final in 24.

Asked as

  • What problem do stream gatherers solve?
  • How is a gatherer different from a collector?
  • How would you batch a stream into fixed-size groups before 24?

Synchronize virtual threads without pinning

standardJEP 491

A virtual thread blocking inside a synchronized block no longer pins its carrier platform thread, removing the main adoption caveat for virtual threads.

The problem it solved

In 21 through 23, blocking inside synchronized pinned the carrier, so a library using synchronized internally could starve the scheduler and silently undo the benefit of virtual threads.

How you did it before

Auditing dependencies for synchronized blocks around blocking calls, or replacing them with ReentrantLock, which does not pin.

Asked as

  • What was thread pinning, and which release fixed it?
  • Why did ReentrantLock avoid the problem when synchronized did not?
  • What are virtual threads, and when do they not help?

Scenario question

  • A service on JDK 21 moved to virtual threads and got no throughput improvement. Profiling shows carrier threads blocked inside a third-party client that synchronizes around its socket calls.

    What is happening, and what are your options?

    What a good answer weighs

    The blocking call inside synchronized pins the carrier, so the pool of carriers is exhausted and virtual threads gain nothing. Options on 21: replace the library, or wrap it so the blocking happens outside the monitor. The clean fix is JDK 24, where JEP 491 removed pinning for synchronized entirely — which is a strong argument for the upgrade rather than a workaround.

Permanently disable the Security Manager

removedJEP 486

The Security Manager can no longer be enabled at all, completing the deprecation begun in 17.

The problem it solved

It remained a maintenance burden and a source of confusion long after it stopped being a viable sandbox for untrusted code.

How you did it before

It was deprecated for removal in 17 but could still be switched on, so code and documentation kept referencing it.

Asked as

  • How do you isolate untrusted code on a modern JVM?
  • Why was the Security Manager unfixable rather than improvable?

    Other versions