1. Introduction

The groovy-concurrent-java module provides Groovy’s concurrent and parallel API as a standalone Java library — no Groovy runtime required.

Java developers get access to:

  • Structured concurrencyAsyncScope, Awaitable

  • PoolsPool, ParallelScope, ConcurrentConfig

  • ActorsActor.reactor(), Actor.stateful()

  • AgentsAgent (thread-safe mutable state)

  • DataflowDataflowVariable, AsyncChannel, BroadcastChannel, ChannelSelect

This module is published as org.apache.groovy:groovy-concurrent-java and contains a filtered subset of the classes from Groovy core. It must not be used alongside the full Groovy runtime — Gradle enforces mutual exclusion via a shared capability, and a runtime warning is logged if both jars are detected on the classpath.

2. Setup

2.1. Gradle

dependencies {
    implementation 'org.apache.groovy:groovy-concurrent-java:6.0.0'
}

2.2. Maven

<dependency>
    <groupId>org.apache.groovy</groupId>
    <artifactId>groovy-concurrent-java</artifactId>
    <version>6.0.0</version>
</dependency>
Do not add groovy-concurrent-java if you already depend on groovy (the full runtime). The concurrent classes are included in core.

3. Structured Concurrency

import groovy.concurrent.AsyncScope;
import groovy.concurrent.Pool;
import org.apache.groovy.runtime.async.AsyncSupport;

// Basic scope — waits for all children before returning
var result = AsyncScope.withScope(scope -> {
    var a = scope.async(() -> fetchUser(id));
    var b = scope.async(() -> fetchOrders(id));
    return Map.of(
        "user", AsyncSupport.await(a),
        "orders", AsyncSupport.await(b)
    );
});

// With a pool for executor isolation
try (var pool = Pool.cpu()) {
    AsyncScope.withScope(pool, scope -> {
        var task = scope.async(() -> computeResult());
        return AsyncSupport.await(task);
    });
}

4. Actors

import groovy.concurrent.Actor;
import org.apache.groovy.runtime.async.AsyncSupport;

// Reactor — stateless, each message produces a reply
var doubler = Actor.<Integer, Integer>reactor(n -> n * 2);
int result = AsyncSupport.await(doubler.sendAndGet(21)); // 42
doubler.stop();

// Stateful — maintains state across messages
var counter = Actor.<String, Integer>stateful(0, (state, msg) -> {
    if ("increment".equals(msg)) return state + 1;
    return state;
});
counter.send("increment");
counter.send("increment");
int count = AsyncSupport.await(counter.sendAndGet("increment")); // 3
counter.stop();

5. Agents

import groovy.concurrent.Agent;
import org.apache.groovy.runtime.async.AsyncSupport;

var counter = Agent.create(0);
counter.send(n -> n + 1);
counter.send(n -> n + 1);
counter.send(n -> n + 1);

int result = AsyncSupport.await(counter.getAsync()); // 3
counter.shutdown();

6. Dataflow Variables

import groovy.concurrent.Awaitable;
import groovy.concurrent.DataflowVariable;
import org.apache.groovy.runtime.async.AsyncSupport;

var x = new DataflowVariable<Integer>();
var y = new DataflowVariable<Integer>();

// z depends on x and y — blocks until both are bound
var z = Awaitable.<Integer>go(() ->
    AsyncSupport.await(x) + AsyncSupport.await(y)
);

// Bind in any order
AsyncSupport.getExecutor().execute(() -> x.bind(10));
AsyncSupport.getExecutor().execute(() -> y.bind(5));

int result = AsyncSupport.await(z); // 15

7. Pools

import groovy.concurrent.ParallelScope;
import groovy.concurrent.Pool;
import org.apache.groovy.runtime.async.AsyncSupport;

var result = ParallelScope.withPool(4, scope -> {
    var tasks = new java.util.ArrayList<groovy.concurrent.Awaitable<Integer>>();
    for (int i = 1; i <= 4; i++) {
        int n = i;
        tasks.add(scope.async(() -> n * 10));
    }
    int sum = 0;
    for (var task : tasks) {
        sum += (int) AsyncSupport.await(task);
    }
    return sum;
}); // 100

8. Features not available in the Java module

The following features require the Groovy runtime and are only available when using the full groovy dependency:

Feature Reason

Dataflows class

Uses Groovy’s propertyMissing for dynamic property access

async { } / await keywords

Groovy language syntax, transformed by the Groovy compiler

defer { } / yield return

Groovy language syntax

for await (x in channel)

Groovy compiler-generated iteration

@ActiveObject / @ActiveMethod

Groovy AST transformation

Parallel collection methods (collectParallel, etc.)

Registered as Groovy extension methods on Collection

AsyncClosureUtils (wrapAsync, wrapAsyncGenerator)

Return Closure subclasses

Closure-based APIs (e.g., scope.async { })

Groovy SAM coercion from Closure to Supplier/Function

Java users have full access to the underlying APIs using java.util.function types (Supplier, Function, Consumer, Predicate, etc.) and explicit AsyncSupport.await() calls instead of the await keyword.