(preview) Java 25 Features – Structured Concurrency Explained

Java 25 Release Overview

As we know, Java follows a 6-month release cycle.
Java 25 was released in September 2025, continuing Oracle’s predictable cadence of feature delivery, while Java 26 is scheduled for release on March 17th, 2026.

One of the most important and developer-impacting features in Java 25 is Structured Concurrency, which fundamentally improves how we write and reason about concurrent code

The Problem with ExecutorService

Before Java 25, most concurrent Java code relied on ExecutorService, Future, and thread pools.

ExecutorService executor = Executors.newFixedThreadPool(2);

Future<User> userFuture = executor.submit(this::loadUser);
Future<Order> orderFuture = executor.submit(this::loadOrder);

User user = userFuture.get();
Order order = orderFuture.get();

At first glance, this looks fine—but it hides several serious problems.

Thread Leak Problem

One of the biggest issues with ExecutorService is thread leakage.

  • Threads can outlive the method that created them
  • Developers must manually call shutdown() or shutdownNow()
  • If an exception occurs early, threads may keep running in the background
  • In production systems, this leads to resource exhaustion
executor.shutdown(); // Often forgotten

Java gives you the tools—but does not enforce structure.

No Control Over Subtask Failure or Completion

Another major issue:

  • If one subtask fails, other subtasks continue running
  • Failures are handled individually, not centrally
  • Partial success is common, but often undesirable

Example:

  • loadUser() fails
  • loadOrder() still runs
  • The method returns incomplete or inconsistent data

There is no built-in policy to say:

  • “Cancel everything if one task fails”
  • “Return the first successful result”
  • “Wait for all tasks no matter what”

Introducing Structured Concurrency (Java 25)

Structured Concurrency solves these problems by enforcing a simple rule:

If a method starts concurrent tasks, it must wait for them to finish.

This creates a parent–child relationship between:

  • The method (parent)
  • The concurrent subtasks (children)

Just like:

  • Method calls
  • try-with-resources blocks

Core Idea: All Tasks Succeed or Fail Together

In structured concurrency:

  • Tasks are executed within a scope
  • The scope defines how success and failure are handled
  • No task can escape the scope

This makes concurrent code:

  • Predictable
  • Safer
  • Easier to reason about

Structured Concurrency – On Success

Scenario: First Successful Result Wins

Sometimes you want:

  • Multiple fallback APIs
  • Redundant services
  • Fastest possible response

In this case:

  • As soon as one task succeeds
  • All other tasks are cancelled

Conceptually:

Task A ──❌
Task B ──✅  ← Winner
Task C ──❌ (cancelled)

This pattern is ideal for:

  • Failover
  • Replicated services
  • Low-latency systems

Structured Concurrency – On Failure

Scenario: All Tasks Must Succeed

This is the most common use case.

  • If any task fails
  • All other tasks are cancelled
  • The failure is propagated to the caller

Conceptually:

Task A ──✅
Task B ──❌ ← Failure
Task C ──⛔ (cancelled)

Java 25 Structured Concurrency – Class Structure

At the core of this feature is:

java.util.concurrent.StructuredTaskScope

Key Concepts

  • open()→ Opens a new StructuredTaskScope that can be used to fork subtasks of type <T>
  • fork() → Starts a subtask (virtual thread)
  • join() → Waits for completion
  • joiner → Defines success/failure policy

Basic Class Structure

try (var scope =
     StructuredTaskScope.open(joiner)) {

    var task1 = scope.fork(this::taskOne);
    var task2 = scope.fork(this::taskTwo);

    return scope.join();
}

Key points:

  • Uses try-with-resources
  • Tasks cannot escape the scope
  • Automatic cancellation and cleanup

Built-in Joiner Types (Conceptually)

Java 25 provides built-in joiners that define completion behavior.

All Tasks Must Succeed

  • Fails fast
  • Cancels remaining tasks on error

Any Task Succeeds

  • First successful result wins
  • Cancels remaining tasks

Await All Tasks

  • Waits for all tasks
  • Does not fail early

Joiners define when to stop and what result to return.


Custom Joiners (Advanced Control)

When built-in policies are not enough, Java 25 allows custom joiners.

Why Custom Joiners?

Use them when you need:

  • Business-specific stopping rules
  • Stop after N successes
  • Stop when a condition is met
  • Partial result aggregation

Custom Joiner Structure

public interface Joiner<T, R> {

    boolean onFork(Subtask<? extends T> subtask);

    boolean onComplete(Subtask<? extends T> subtask);

    R result() throws Throwable;
}

What Each Method Does

  • onFork(…)
    • Called when a task is created
    • Decide whether to continue or cancel
  • onComplete(…)
    • Called when a task finishes
    • Decide whether to stop remaining tasks
  • result()
    • Produces the final result
    • Or throws an exception

This gives full control over task coordination.


Why This Matters

Structured Concurrency in Java 25:

  • Eliminates thread leaks
  • Centralizes error handling
  • Works perfectly with virtual threads
  • Makes concurrent code readable and safe
  • Fits naturally into modern microservices

This is one of the most important improvements to Java concurrency in years.

Leave a comment