Structured Concurrency: Building a Custom Joiner
In the last post, we explored how different Joiners change the behavior of a Structured Concurrency scope. Today, let’s take the next step 🚀.
Instead of using a built-in Joiner…
we’re going to build our own.
And not just any Joiner, one that only succeeds if 75% of the subtasks complete successfully. Perfect for real-world scenarios where “almost all” is good enough.
What Exactly Is a Joiner? 🤝
A Joiner defines how results from multiple subtasks are combined at the end of a scope. It gives you control over:
✔ How to collect results
✔ How to treat failures
✔ When the whole operation is “successful”
Most Joiners require all tasks to succeed, but real systems are messy. Sometimes a single failure shouldn’t break everything. That’s where custom Joiners shine
A Custom Joiner That Requires 75% Success 🧠
public final class SeventyFivePercentJoiner<T>
implements StructuredTaskScope.Joiner<T, List<T>> {
private final AtomicInteger totalForked = new AtomicInteger(0);
private final AtomicInteger successCount = new AtomicInteger(0);
private final Queue<T> successResults = new ConcurrentLinkedQueue<>();
@Override
public boolean onFork(StructuredTaskScope.Subtask<? extends T> subtask) {
totalForked.incrementAndGet();
return false;
}
@Override
public boolean onComplete(StructuredTaskScope.Subtask<? extends T> subtask) {
if (subtask.state() == State.SUCCESS) {
successResults.add(subtask.get());
successCount.incrementAndGet();
}
return false;
}
@Override
public List<T> result() throws Throwable {
int total = totalForked.get();
if (total == 0) {
throw new IllegalStateException(”No subtasks were forked”);
}
int succeeded = successCount.get();
double successRate = (double) succeeded / total;
if (successRate >= 0.75) {
return new ArrayList<>(successResults);
}
throw new IllegalStateException(
String.format(”Joiner failed: success rate %.2f%% (%d/%d)”,
successRate * 100, succeeded, total));
}
}Explanation 📝
onForkincrements the total count as subtasks are created.onCompleteruns in the subtask thread and must be thread-safe, we useAtomicInteger+ConcurrentLinkedQueue.result()computes final success rate and either returns a snapshot list of successful results or throws to signal failure to the caller.
Conclusion & Next Step 🚀
Custom Joiners open the door to smarter parallel execution. With just a bit of logic, you can shape how your application behaves under real-world pressure, and Structured Concurrency makes it simple.
If you found this example useful, make sure to subscribe, leave a like, or share this post with another Java dev 🧡.
References
JEP 505 — Structured Concurrency (JDK 25 preview). openjdk.org
StructuredTaskScope.Joiner(JDK 25 API docs). Oracle Docs


