From f97367b56b2bcead1be47bf03fd7b33d196571ec Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 21 Jul 2026 16:45:34 +0200 Subject: [PATCH 1/4] refactor(workqueue,stream): require an injected handler executor Remove the shared virtual-thread executor default (Executors.newVirtualThread- PerTaskExecutor, a JDK 21 API) from AbstractWorkQueue and AbstractMessageStream. Handlers now run on an executor supplied via withHandlerExecutor(), which is mandatory - startProcessing() fails fast if none was set. This drops the only Java 21 dependency in these modules, so they compile and run on Java 17. Micronaut consumers already inject the @Named(BLOCKING) executor before adding a consumer; test fixtures inject a shared daemon pool (TestWorkerPool). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Paolo Di Tommaso --- .../data/stream/AbstractMessageStream.java | 38 +++++++++---------- .../io/seqera/data/stream/TestStream.groovy | 2 + .../seqera/data/stream/TunableStream.groovy | 1 + .../seqera/data/stream/TestPlainStream.java | 1 + .../io/seqera/data/stream/TestWorkerPool.java | 36 ++++++++++++++++++ .../io/seqera/data/workqueue/TestQueue.groovy | 2 + .../seqera/data/workqueue/TunableQueue.groovy | 1 + .../seqera/data/workqueue/TestWorkerPool.java | 36 ++++++++++++++++++ .../data/workqueue/AbstractWorkQueue.java | 38 +++++++++---------- .../io/seqera/data/workqueue/TestQueue.groovy | 2 + .../seqera/data/workqueue/TunableQueue.groovy | 1 + .../seqera/data/workqueue/TestPlainQueue.java | 1 + .../seqera/data/workqueue/TestWorkerPool.java | 36 ++++++++++++++++++ 13 files changed, 155 insertions(+), 40 deletions(-) create mode 100644 lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestWorkerPool.java create mode 100644 lib-data-workqueue-redis/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java create mode 100644 lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java diff --git a/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java b/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java index d3dd6a0..a0e2ab8 100644 --- a/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java +++ b/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java @@ -20,9 +20,9 @@ import java.io.Closeable; import java.time.Duration; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -148,16 +148,14 @@ String key() { */ private final Map active = new ConcurrentHashMap<>(); - /** Shared virtual-thread executor used when no handler executor is supplied. */ - private static final ExecutorService DEFAULT_WORKERS = Executors.newVirtualThreadPerTaskExecutor(); - /** - * Executor that runs the message handlers, on virtual threads. Defaults to a shared - * virtual-thread-per-task executor; Micronaut consumers replace it with the injected - * {@code BLOCKING} executor via {@link #withHandlerExecutor}. Handler concurrency is - * bounded by {@link #slots}, not by this executor, so it is never sized or shut down here. + * Executor that runs the message handlers. Supplied by the consumer via + * {@link #withHandlerExecutor} before the first {@link #addConsumer} — there is no default, + * so {@link #startProcessing()} fails fast if it was never set. Micronaut consumers pass the + * injected {@code BLOCKING} executor. Handler concurrency is bounded by {@link #slots}, not by + * this executor, so it is never sized or shut down here. */ - private volatile ExecutorService pool = DEFAULT_WORKERS; + private volatile ExecutorService pool; /** * Gates new intake: a permit is acquired when a lease is picked up and held for the @@ -329,8 +327,10 @@ public void addConsumer(String streamId, MessageConsumer consumer) { * consumer is registered. */ private void startProcessing() { + // a handler executor must be supplied via withHandlerExecutor() before processing starts + Objects.requireNonNull(pool, "Handler executor not set - call withHandlerExecutor() before addConsumer()"); // 'slots' — not the executor — bounds how many commands may be in flight at once; - // handlers run on cheap virtual threads, so the cap is a memory/heartbeat ceiling. + // the cap is a memory/heartbeat ceiling, independent of the executor's threading model. this.slots = new Semaphore(Math.max(1, concurrency())); this.scheduler = new ScheduledThreadPoolExecutor(1, daemonFactory(name() + "-repoll-" + count.get())); this.heartbeat = new ScheduledThreadPoolExecutor(1, daemonFactory(name() + "-heartbeat-" + count.get())); @@ -340,18 +340,16 @@ private void startProcessing() { } /** - * Supply the executor used to run message handlers. Micronaut-managed consumers pass the - * injected {@code @Named(TaskExecutors.BLOCKING)} {@link ExecutorService} (virtual-thread - * backed on JDK 21) before the first {@link #addConsumer}. When not - * supplied, a shared virtual-thread executor is used. The executor is never shut down by - * {@link #close()} (it is shared / container-managed). + * Supply the executor used to run message handlers. Consumers must call this + * before the first {@link #addConsumer} — there is no default executor. + * Micronaut-managed consumers pass the injected {@code @Named(TaskExecutors.BLOCKING)} + * {@link ExecutorService}. The executor is never shut down by {@link #close()} + * (it is shared / container-managed). * - * @param executor the shared handler executor; ignored if {@code null} + * @param executor the shared handler executor; must not be {@code null} */ - public void withHandlerExecutor(@Nullable ExecutorService executor) { - if (executor != null) { - this.pool = executor; - } + public void withHandlerExecutor(ExecutorService executor) { + this.pool = Objects.requireNonNull(executor, "Handler executor cannot be null"); } private static ThreadFactory daemonFactory(String prefix) { diff --git a/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TestStream.groovy b/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TestStream.groovy index e897684..05ab00c 100644 --- a/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TestStream.groovy +++ b/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TestStream.groovy @@ -34,10 +34,12 @@ class TestStream extends AbstractMessageStream { TestStream(MessageStream target) { super(target) + withHandlerExecutor(TestWorkerPool.INSTANCE) } TestStream(MessageStream target, StreamMetrics metrics) { super(target, metrics) + withHandlerExecutor(TestWorkerPool.INSTANCE) } static TestStream withRegistry(MessageStream target, MeterRegistry registry) { diff --git a/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TunableStream.groovy b/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TunableStream.groovy index 94d2076..16c3c31 100644 --- a/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TunableStream.groovy +++ b/lib-data-stream-redis/src/test/groovy/io/seqera/data/stream/TunableStream.groovy @@ -38,6 +38,7 @@ class TunableStream extends AbstractMessageStream { TunableStream(Map opts = [:], MessageStream target) { super(target) + withHandlerExecutor(TestWorkerPool.INSTANCE) this.workers = (opts.concurrency ?: 1) as int this.pollDelay = (opts.pollInterval ?: Duration.ofSeconds(1)) as Duration this.hbInterval = (opts.heartbeatInterval ?: Duration.ofSeconds(20)) as Duration diff --git a/lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestPlainStream.java b/lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestPlainStream.java index 040a25b..71370ee 100644 --- a/lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestPlainStream.java +++ b/lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestPlainStream.java @@ -33,6 +33,7 @@ public class TestPlainStream extends AbstractMessageStream { public TestPlainStream(MessageStream target) { super(target); + withHandlerExecutor(TestWorkerPool.INSTANCE); } @Override diff --git a/lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestWorkerPool.java b/lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestWorkerPool.java new file mode 100644 index 0000000..3ad8ad7 --- /dev/null +++ b/lib-data-stream-redis/src/test/java/io/seqera/data/stream/TestWorkerPool.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.seqera.data.stream; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Shared daemon handler executor for tests. {@link AbstractMessageStream} no longer ships a + * built-in default executor (handlers must be supplied via {@code withHandlerExecutor}), so the + * test fixtures inject this one. Daemon threads so it never keeps the test JVM alive. + */ +public final class TestWorkerPool { + private TestWorkerPool() {} + + public static final ExecutorService INSTANCE = Executors.newCachedThreadPool(r -> { + Thread t = new Thread(r, "test-handler"); + t.setDaemon(true); + return t; + }); +} diff --git a/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy b/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy index 909ccb1..fa5f61f 100644 --- a/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy +++ b/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy @@ -34,10 +34,12 @@ class TestQueue extends AbstractWorkQueue { TestQueue(WorkQueue target) { super(target) + withHandlerExecutor(TestWorkerPool.INSTANCE) } TestQueue(WorkQueue target, QueueMetrics metrics) { super(target, metrics) + withHandlerExecutor(TestWorkerPool.INSTANCE) } static TestQueue withRegistry(WorkQueue target, MeterRegistry registry) { diff --git a/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy b/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy index 8da20f2..4e2523a 100644 --- a/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy +++ b/lib-data-workqueue-redis/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy @@ -38,6 +38,7 @@ class TunableQueue extends AbstractWorkQueue { TunableQueue(Map opts = [:], WorkQueue target) { super(target) + withHandlerExecutor(TestWorkerPool.INSTANCE) this.workers = (opts.concurrency ?: 1) as int this.pollDelay = (opts.pollInterval ?: Duration.ofSeconds(1)) as Duration this.hbInterval = (opts.heartbeatInterval ?: Duration.ofSeconds(20)) as Duration diff --git a/lib-data-workqueue-redis/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java b/lib-data-workqueue-redis/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java new file mode 100644 index 0000000..af5bd1e --- /dev/null +++ b/lib-data-workqueue-redis/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.seqera.data.workqueue; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Shared daemon handler executor for tests. {@link AbstractWorkQueue} no longer ships a + * built-in default executor (handlers must be supplied via {@code withHandlerExecutor}), so the + * test fixtures inject this one. Daemon threads so it never keeps the test JVM alive. + */ +public final class TestWorkerPool { + private TestWorkerPool() {} + + public static final ExecutorService INSTANCE = Executors.newCachedThreadPool(r -> { + Thread t = new Thread(r, "test-handler"); + t.setDaemon(true); + return t; + }); +} diff --git a/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java b/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java index 96e7ad7..cc4998e 100644 --- a/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java +++ b/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java @@ -20,9 +20,9 @@ import java.io.Closeable; import java.time.Duration; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -148,16 +148,14 @@ String key() { */ private final Map active = new ConcurrentHashMap<>(); - /** Shared virtual-thread executor used when no handler executor is supplied. */ - private static final ExecutorService DEFAULT_WORKERS = Executors.newVirtualThreadPerTaskExecutor(); - /** - * Executor that runs the message handlers, on virtual threads. Defaults to a shared - * virtual-thread-per-task executor; Micronaut consumers replace it with the injected - * {@code BLOCKING} executor via {@link #withHandlerExecutor}. Handler concurrency is - * bounded by {@link #slots}, not by this executor, so it is never sized or shut down here. + * Executor that runs the message handlers. Supplied by the consumer via + * {@link #withHandlerExecutor} before the first {@link #addConsumer} — there is no default, + * so {@link #startProcessing()} fails fast if it was never set. Micronaut consumers pass the + * injected {@code BLOCKING} executor. Handler concurrency is bounded by {@link #slots}, not by + * this executor, so it is never sized or shut down here. */ - private volatile ExecutorService pool = DEFAULT_WORKERS; + private volatile ExecutorService pool; /** * Gates new intake: a permit is acquired when a lease is picked up and held for the @@ -329,8 +327,10 @@ public void addConsumer(String queueId, MessageConsumer consumer) { * consumer is registered. */ private void startProcessing() { + // a handler executor must be supplied via withHandlerExecutor() before processing starts + Objects.requireNonNull(pool, "Handler executor not set - call withHandlerExecutor() before addConsumer()"); // 'slots' — not the executor — bounds how many commands may be in flight at once; - // handlers run on cheap virtual threads, so the cap is a memory/heartbeat ceiling. + // the cap is a memory/heartbeat ceiling, independent of the executor's threading model. this.slots = new Semaphore(Math.max(1, concurrency())); this.scheduler = new ScheduledThreadPoolExecutor(1, daemonFactory(name() + "-repoll-" + count.get())); this.heartbeat = new ScheduledThreadPoolExecutor(1, daemonFactory(name() + "-heartbeat-" + count.get())); @@ -340,18 +340,16 @@ private void startProcessing() { } /** - * Supply the executor used to run message handlers. Micronaut-managed consumers pass the - * injected {@code @Named(TaskExecutors.BLOCKING)} {@link ExecutorService} (virtual-thread - * backed on JDK 21) before the first {@link #addConsumer}. When not - * supplied, a shared virtual-thread executor is used. The executor is never shut down by - * {@link #close()} (it is shared / container-managed). + * Supply the executor used to run message handlers. Consumers must call this + * before the first {@link #addConsumer} — there is no default executor. + * Micronaut-managed consumers pass the injected {@code @Named(TaskExecutors.BLOCKING)} + * {@link ExecutorService}. The executor is never shut down by {@link #close()} + * (it is shared / container-managed). * - * @param executor the shared handler executor; ignored if {@code null} + * @param executor the shared handler executor; must not be {@code null} */ - public void withHandlerExecutor(@Nullable ExecutorService executor) { - if (executor != null) { - this.pool = executor; - } + public void withHandlerExecutor(ExecutorService executor) { + this.pool = Objects.requireNonNull(executor, "Handler executor cannot be null"); } private static ThreadFactory daemonFactory(String prefix) { diff --git a/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy b/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy index 909ccb1..fa5f61f 100644 --- a/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy +++ b/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TestQueue.groovy @@ -34,10 +34,12 @@ class TestQueue extends AbstractWorkQueue { TestQueue(WorkQueue target) { super(target) + withHandlerExecutor(TestWorkerPool.INSTANCE) } TestQueue(WorkQueue target, QueueMetrics metrics) { super(target, metrics) + withHandlerExecutor(TestWorkerPool.INSTANCE) } static TestQueue withRegistry(WorkQueue target, MeterRegistry registry) { diff --git a/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy b/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy index 8da20f2..4e2523a 100644 --- a/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy +++ b/lib-data-workqueue/src/test/groovy/io/seqera/data/workqueue/TunableQueue.groovy @@ -38,6 +38,7 @@ class TunableQueue extends AbstractWorkQueue { TunableQueue(Map opts = [:], WorkQueue target) { super(target) + withHandlerExecutor(TestWorkerPool.INSTANCE) this.workers = (opts.concurrency ?: 1) as int this.pollDelay = (opts.pollInterval ?: Duration.ofSeconds(1)) as Duration this.hbInterval = (opts.heartbeatInterval ?: Duration.ofSeconds(20)) as Duration diff --git a/lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestPlainQueue.java b/lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestPlainQueue.java index 5ddf5af..cb96ec3 100644 --- a/lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestPlainQueue.java +++ b/lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestPlainQueue.java @@ -33,6 +33,7 @@ public class TestPlainQueue extends AbstractWorkQueue { public TestPlainQueue(WorkQueue target) { super(target); + withHandlerExecutor(TestWorkerPool.INSTANCE); } @Override diff --git a/lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java b/lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java new file mode 100644 index 0000000..af5bd1e --- /dev/null +++ b/lib-data-workqueue/src/test/java/io/seqera/data/workqueue/TestWorkerPool.java @@ -0,0 +1,36 @@ +/* + * Copyright 2026, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.seqera.data.workqueue; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Shared daemon handler executor for tests. {@link AbstractWorkQueue} no longer ships a + * built-in default executor (handlers must be supplied via {@code withHandlerExecutor}), so the + * test fixtures inject this one. Daemon threads so it never keeps the test JVM alive. + */ +public final class TestWorkerPool { + private TestWorkerPool() {} + + public static final ExecutorService INSTANCE = Executors.newCachedThreadPool(r -> { + Thread t = new Thread(r, "test-handler"); + t.setDaemon(true); + return t; + }); +} From 5eae9f5fc037595639c5fd9bb3a079540a1e28a5 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 21 Jul 2026 16:45:44 +0200 Subject: [PATCH 2/4] build(java): target Java 17 via --release; expose lib-data-workqueue as api Convention plugins now target Java 17: javac --release 17 (which also caps the API surface to the Java 17 stdlib) and source/target 17 for Groovy. The target is set once in java-library-conventions - applied by every module - to avoid duplicate, order-dependent configureEach across the sibling conventions. Also change lib-cmd-queue-redis's dependency on lib-data-workqueue from implementation to api: CommandQueue publicly extends AbstractWorkQueue and exposes WorkQueue / MessageConsumer, so consumers subclassing it need those types on the compile classpath. It previously resolved only via the published POM's runtime scope and broke Gradle composite builds. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Paolo Di Tommaso --- .../io.seqera.groovy-common-conventions.gradle | 5 +++-- .../io.seqera.java-library-conventions.gradle | 14 ++++++++++++-- ...io.seqera.java-test-fixtures-conventions.gradle | 5 +++-- lib-cmd-queue-redis/build.gradle | 5 +++-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle index f55b4a8..23d6593 100644 --- a/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle +++ b/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle @@ -35,8 +35,9 @@ java { toolchain { languageVersion = JavaLanguageVersion.of(21) } - sourceCompatibility = 17 - targetCompatibility = 17 + // Compile target (--release 17 / source-target) is set once in + // io.seqera.java-library-conventions, which every module applies. Keeping it + // there avoids duplicate/conflicting configureEach across sibling conventions. } group = 'io.seqera' diff --git a/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle index cdbdf2a..81bc90f 100644 --- a/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle +++ b/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle @@ -35,8 +35,18 @@ java { toolchain { languageVersion = JavaLanguageVersion.of(21) } - sourceCompatibility = 17 - targetCompatibility = 17 +} + +// Target Java 17. For Java sources use --release, which also constrains the API +// surface to the Java 17 stdlib; Groovy does not honor --release, so its bytecode +// level is pinned via source/target. --release and -source/-target cannot coexist +// on the same javac task, hence the split across compile task types. +tasks.withType(JavaCompile).configureEach { + options.release = 17 +} +tasks.withType(GroovyCompile).configureEach { + sourceCompatibility = '17' + targetCompatibility = '17' } test { diff --git a/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle index 7a5d67f..cf83165 100644 --- a/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle +++ b/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle @@ -37,8 +37,9 @@ java { toolchain { languageVersion = JavaLanguageVersion.of(21) } - sourceCompatibility = 17 - targetCompatibility = 17 + // Compile target (--release 17 / source-target) is set once in + // io.seqera.java-library-conventions, which every module applies. Keeping it + // there avoids duplicate/conflicting configureEach across sibling conventions. } test { diff --git a/lib-cmd-queue-redis/build.gradle b/lib-cmd-queue-redis/build.gradle index d09bf0f..a5490fa 100644 --- a/lib-cmd-queue-redis/build.gradle +++ b/lib-cmd-queue-redis/build.gradle @@ -32,8 +32,9 @@ dependencies { // JSON serialization implementation project(':lib-serde-jackson') - // Work queue - implementation project(':lib-data-workqueue') + // Work queue - 'api' because CommandQueue publicly extends AbstractWorkQueue and exposes + // WorkQueue / MessageConsumer from this module in its public API (consumers subclass it). + api project(':lib-data-workqueue') implementation project(':lib-data-workqueue-redis') implementation project(':lib-serde-moshi') From 888faa3b18cad6ef07e7faea9e9b0972f570ef66 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Wed, 22 Jul 2026 21:08:45 +0200 Subject: [PATCH 3/4] build(java): Java 25 for Micronaut libs, Java 17 for the rest Restore the two-convention split (superseding the uniform --release 17 from the earlier commit). libseqera's toolchain moves to Java 25 and the compile target is chosen per module type: - io.seqera.java-library-conventions: plain libraries -> javac --release 17 (+ Groovy source/target 17). These include everything the Nextflow Java 17 runtime consumes (lib-httpx, lib-retry, lib-trace, wave-api, wave-utils, lib-cloudinfo, lib-platform-oidc). - io.seqera.micronaut-library-conventions (new): libraries that depend on Micronaut -> target Java 25. Micronaut runs on the Java 25 service runtime (and Micronaut 5 requires Java 25), so these are never loaded on the Nextflow Java 17 runtime. Applied to the 21 Micronaut modules. Bumps Groovy 4.0.24 -> 4.0.31: the older Groovy cannot run on a JDK 25 toolchain ("Unsupported class file major version 69"). Verified: Micronaut libs compile to bytecode 69 (Java 25), plain libs to 61 (Java 17); full compile and the affected module test suites (incl. Redis testcontainers) pass. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Paolo Di Tommaso --- ...io.seqera.groovy-common-conventions.gradle | 2 +- ...o.seqera.groovy-library-conventions.gradle | 2 +- .../io.seqera.java-library-conventions.gradle | 8 ++-- ...qera.java-test-fixtures-conventions.gradle | 8 ++-- ...eqera.micronaut-library-conventions.gradle | 39 +++++++++++++++++++ lib-cache-tiered-redis/build.gradle | 2 +- lib-cmd-queue-redis/build.gradle | 2 +- lib-data-broadcast-redis/build.gradle | 4 +- lib-data-count-redis/build.gradle | 4 +- lib-data-queue-redis/build.gradle | 8 ++-- lib-data-range-redis/build.gradle | 4 +- lib-data-store-future-redis/build.gradle | 2 +- lib-data-store-state-redis/build.gradle | 2 +- lib-data-stream-redis/build.gradle | 10 ++--- lib-data-workqueue-redis/build.gradle | 10 ++--- lib-data-workqueue/build.gradle | 10 ++--- lib-fixtures-redis/build.gradle | 2 +- lib-hashx/build.gradle | 2 +- lib-jedis-pool/build.gradle | 4 +- lib-lock-redis/build.gradle | 2 +- lib-lock/build.gradle | 2 +- lib-mail/build.gradle | 8 ++-- lib-node-id-redis/build.gradle | 4 +- lib-pairing/build.gradle | 2 +- lib-platform-oidc/build.gradle | 2 +- lib-util-http/build.gradle | 2 +- lib-util-tsid/build.gradle | 4 +- micronaut-cache-redis/build.gradle | 2 +- 28 files changed, 96 insertions(+), 57 deletions(-) create mode 100644 buildSrc/src/main/groovy/io.seqera.micronaut-library-conventions.gradle diff --git a/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle index 23d6593..9a388fa 100644 --- a/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle +++ b/buildSrc/src/main/groovy/io.seqera.groovy-common-conventions.gradle @@ -33,7 +33,7 @@ repositories { java { // these settings apply to all jvm tooling, including groovy toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } // Compile target (--release 17 / source-target) is set once in // io.seqera.java-library-conventions, which every module applies. Keeping it diff --git a/buildSrc/src/main/groovy/io.seqera.groovy-library-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.groovy-library-conventions.gradle index 02d0d61..93f59b1 100644 --- a/buildSrc/src/main/groovy/io.seqera.groovy-library-conventions.gradle +++ b/buildSrc/src/main/groovy/io.seqera.groovy-library-conventions.gradle @@ -28,7 +28,7 @@ plugins { } dependencies { - implementation "org.apache.groovy:groovy:4.0.24" + implementation "org.apache.groovy:groovy:4.0.31" } group = 'io.seqera' diff --git a/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle index 81bc90f..fd745e6 100644 --- a/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle +++ b/buildSrc/src/main/groovy/io.seqera.java-library-conventions.gradle @@ -33,7 +33,7 @@ repositories { java { // these settings apply to all jvm tooling, including groovy toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } } @@ -64,9 +64,9 @@ dependencies { testImplementation 'ch.qos.logback:logback-core:1.5.25' testImplementation 'ch.qos.logback:logback-classic:1.5.25' - testImplementation "org.apache.groovy:groovy:4.0.24" - testImplementation "org.apache.groovy:groovy-nio:4.0.24" - testImplementation "org.apache.groovy:groovy-test:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" + testImplementation "org.apache.groovy:groovy-nio:4.0.31" + testImplementation "org.apache.groovy:groovy-test:4.0.31" testImplementation "cglib:cglib-nodep:3.3.0" testImplementation "org.objenesis:objenesis:3.4" testImplementation "org.spockframework:spock-core:2.3-groovy-4.0" diff --git a/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle index cf83165..8d10fae 100644 --- a/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle +++ b/buildSrc/src/main/groovy/io.seqera.java-test-fixtures-conventions.gradle @@ -35,7 +35,7 @@ repositories { java { // these settings apply to all jvm tooling, including groovy toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } // Compile target (--release 17 / source-target) is set once in // io.seqera.java-library-conventions, which every module applies. Keeping it @@ -57,9 +57,9 @@ dependencies { testFixturesImplementation 'ch.qos.logback:logback-core:1.5.25' testFixturesImplementation 'ch.qos.logback:logback-classic:1.5.25' - testFixturesImplementation "org.apache.groovy:groovy:4.0.24" - testFixturesImplementation "org.apache.groovy:groovy-nio:4.0.24" - testFixturesImplementation "org.apache.groovy:groovy-test:4.0.24" + testFixturesImplementation "org.apache.groovy:groovy:4.0.31" + testFixturesImplementation "org.apache.groovy:groovy-nio:4.0.31" + testFixturesImplementation "org.apache.groovy:groovy-test:4.0.31" testFixturesImplementation "cglib:cglib-nodep:3.3.0" testFixturesImplementation "org.objenesis:objenesis:3.4" testFixturesImplementation "org.spockframework:spock-core:2.3-groovy-4.0" diff --git a/buildSrc/src/main/groovy/io.seqera.micronaut-library-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.micronaut-library-conventions.gradle new file mode 100644 index 0000000..91a72b7 --- /dev/null +++ b/buildSrc/src/main/groovy/io.seqera.micronaut-library-conventions.gradle @@ -0,0 +1,39 @@ +/* + * Copyright 2026, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +plugins { + // Inherit the standard library setup (java-library, groovy, maven-publish, + // test deps, publishing) and its --release 17 default. + id 'io.seqera.java-library-conventions' +} + +group = 'io.seqera' + +// Libraries that depend on Micronaut target Java 25: Micronaut runs on the Java 25 +// service runtime (and Micronaut 5 requires it), so these are never loaded by the +// Nextflow Java 17 runtime. Override the java-library-conventions --release 17 +// default; --release is dropped since it cannot coexist with -source/-target on the +// same javac task. +tasks.withType(JavaCompile).configureEach { + options.release = null + sourceCompatibility = '25' + targetCompatibility = '25' +} +tasks.withType(GroovyCompile).configureEach { + sourceCompatibility = '25' + targetCompatibility = '25' +} diff --git a/lib-cache-tiered-redis/build.gradle b/lib-cache-tiered-redis/build.gradle index f56e2dc..03167de 100644 --- a/lib-cache-tiered-redis/build.gradle +++ b/lib-cache-tiered-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id "io.micronaut.minimal.library" version '4.5.3' } diff --git a/lib-cmd-queue-redis/build.gradle b/lib-cmd-queue-redis/build.gradle index a5490fa..8bfded3 100644 --- a/lib-cmd-queue-redis/build.gradle +++ b/lib-cmd-queue-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id "io.micronaut.minimal.library" version '4.5.3' } diff --git a/lib-data-broadcast-redis/build.gradle b/lib-data-broadcast-redis/build.gradle index c252b68..aeb7a90 100644 --- a/lib-data-broadcast-redis/build.gradle +++ b/lib-data-broadcast-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'groovy' id "io.micronaut.minimal.library" version '4.5.3' } @@ -33,7 +33,7 @@ dependencies { implementation "io.micronaut:micronaut-runtime:${micronautCoreVersion}" // Groovy dependencies only for tests - testImplementation "org.apache.groovy:groovy:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" testImplementation testFixtures(project(':lib-fixtures-redis')) testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" testImplementation "io.micronaut.test:micronaut-test-spock:${micronautTestVersion}" diff --git a/lib-data-count-redis/build.gradle b/lib-data-count-redis/build.gradle index f2fc86f..3117690 100644 --- a/lib-data-count-redis/build.gradle +++ b/lib-data-count-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ @@ -35,7 +35,7 @@ dependencies { implementation "io.micronaut:micronaut-runtime:${micronautCoreVersion}" testImplementation testFixtures(project(':lib-fixtures-redis')) - testImplementation "org.apache.groovy:groovy:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" testImplementation "io.micronaut.test:micronaut-test-spock:${micronautTestVersion}" testImplementation 'org.testcontainers:testcontainers:1.21.4' diff --git a/lib-data-queue-redis/build.gradle b/lib-data-queue-redis/build.gradle index 9a9cc69..623db93 100644 --- a/lib-data-queue-redis/build.gradle +++ b/lib-data-queue-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ @@ -35,9 +35,9 @@ dependencies { compileOnly "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" implementation "ch.qos.logback:logback-classic:1.5.25" - implementation "org.apache.groovy:groovy:4.0.24" - implementation "org.apache.groovy:groovy-nio:4.0.24" - implementation "org.apache.groovy:groovy-templates:4.0.24" + implementation "org.apache.groovy:groovy:4.0.31" + implementation "org.apache.groovy:groovy-nio:4.0.31" + implementation "org.apache.groovy:groovy-templates:4.0.31" implementation "io.micronaut:micronaut-runtime:${micronautCoreVersion}" implementation "io.micronaut:micronaut-websocket:${micronautCoreVersion}" implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' diff --git a/lib-data-range-redis/build.gradle b/lib-data-range-redis/build.gradle index 794a4ec..1e3dab0 100644 --- a/lib-data-range-redis/build.gradle +++ b/lib-data-range-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ @@ -34,7 +34,7 @@ dependencies { implementation "io.micronaut:micronaut-runtime:${micronautCoreVersion}" testImplementation testFixtures(project(':lib-fixtures-redis')) - testImplementation "org.apache.groovy:groovy:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" testImplementation "io.micronaut.test:micronaut-test-spock:${micronautTestVersion}" testImplementation 'org.testcontainers:testcontainers:1.21.4' diff --git a/lib-data-store-future-redis/build.gradle b/lib-data-store-future-redis/build.gradle index b950800..c042707 100644 --- a/lib-data-store-future-redis/build.gradle +++ b/lib-data-store-future-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ diff --git a/lib-data-store-state-redis/build.gradle b/lib-data-store-state-redis/build.gradle index 9ff3c2e..74b29dc 100644 --- a/lib-data-store-state-redis/build.gradle +++ b/lib-data-store-state-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ diff --git a/lib-data-stream-redis/build.gradle b/lib-data-stream-redis/build.gradle index db9b3b4..668a0c5 100644 --- a/lib-data-stream-redis/build.gradle +++ b/lib-data-stream-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'groovy' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ @@ -45,10 +45,10 @@ dependencies { testImplementation "io.micrometer:micrometer-core:1.12.4" // Groovy dependencies only for tests - testImplementation "org.apache.groovy:groovy:4.0.24" - testImplementation "org.apache.groovy:groovy-nio:4.0.24" - testImplementation "org.apache.groovy:groovy-templates:4.0.24" - testImplementation "org.apache.groovy:groovy-json:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" + testImplementation "org.apache.groovy:groovy-nio:4.0.31" + testImplementation "org.apache.groovy:groovy-templates:4.0.31" + testImplementation "org.apache.groovy:groovy-json:4.0.31" testImplementation project(':lib-lang') testImplementation testFixtures(project(':lib-fixtures-redis')) testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" diff --git a/lib-data-workqueue-redis/build.gradle b/lib-data-workqueue-redis/build.gradle index e5e2d62..5ded163 100644 --- a/lib-data-workqueue-redis/build.gradle +++ b/lib-data-workqueue-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'groovy' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ @@ -37,10 +37,10 @@ dependencies { // Groovy dependencies only for tests testImplementation "io.micrometer:micrometer-core:1.12.4" - testImplementation "org.apache.groovy:groovy:4.0.24" - testImplementation "org.apache.groovy:groovy-nio:4.0.24" - testImplementation "org.apache.groovy:groovy-templates:4.0.24" - testImplementation "org.apache.groovy:groovy-json:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" + testImplementation "org.apache.groovy:groovy-nio:4.0.31" + testImplementation "org.apache.groovy:groovy-templates:4.0.31" + testImplementation "org.apache.groovy:groovy-json:4.0.31" testImplementation project(':lib-lang') testImplementation project(':lib-random') testImplementation project(':lib-serde') diff --git a/lib-data-workqueue/build.gradle b/lib-data-workqueue/build.gradle index d2dc25c..fc5fbb4 100644 --- a/lib-data-workqueue/build.gradle +++ b/lib-data-workqueue/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'groovy' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ @@ -40,10 +40,10 @@ dependencies { testImplementation "io.micrometer:micrometer-core:1.12.4" // Groovy dependencies only for tests - testImplementation "org.apache.groovy:groovy:4.0.24" - testImplementation "org.apache.groovy:groovy-nio:4.0.24" - testImplementation "org.apache.groovy:groovy-templates:4.0.24" - testImplementation "org.apache.groovy:groovy-json:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" + testImplementation "org.apache.groovy:groovy-nio:4.0.31" + testImplementation "org.apache.groovy:groovy-templates:4.0.31" + testImplementation "org.apache.groovy:groovy-json:4.0.31" testImplementation project(':lib-lang') testImplementation project(':lib-random') testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" diff --git a/lib-fixtures-redis/build.gradle b/lib-fixtures-redis/build.gradle index cf19b07..6cb7f66 100644 --- a/lib-fixtures-redis/build.gradle +++ b/lib-fixtures-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id 'io.seqera.java-test-fixtures-conventions' id "io.micronaut.minimal.library" version '4.5.3' diff --git a/lib-hashx/build.gradle b/lib-hashx/build.gradle index f82cf33..1b1964b 100644 --- a/lib-hashx/build.gradle +++ b/lib-hashx/build.gradle @@ -25,6 +25,6 @@ version = "${project.file('VERSION').text.trim()}" dependencies { // Test dependencies - testImplementation 'org.apache.groovy:groovy:4.0.24' + testImplementation 'org.apache.groovy:groovy:4.0.31' testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0' } diff --git a/lib-jedis-pool/build.gradle b/lib-jedis-pool/build.gradle index c17a4db..e7be998 100644 --- a/lib-jedis-pool/build.gradle +++ b/lib-jedis-pool/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id "io.micronaut.minimal.library" version '4.5.3' } @@ -32,7 +32,7 @@ dependencies { compileOnly "io.micrometer:micrometer-core:1.12.4" implementation "org.slf4j:slf4j-api:2.0.12" - testImplementation "org.apache.groovy:groovy:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" testImplementation "io.micronaut.test:micronaut-test-spock:${micronautTestVersion}" testImplementation "io.micrometer:micrometer-core:1.12.4" diff --git a/lib-lock-redis/build.gradle b/lib-lock-redis/build.gradle index 5ba3044..37853fb 100644 --- a/lib-lock-redis/build.gradle +++ b/lib-lock-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id "io.micronaut.minimal.library" version "4.5.3" } diff --git a/lib-lock/build.gradle b/lib-lock/build.gradle index 3ca22b8..7563a9c 100644 --- a/lib-lock/build.gradle +++ b/lib-lock/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id "io.micronaut.minimal.library" version "4.5.3" } diff --git a/lib-mail/build.gradle b/lib-mail/build.gradle index 8259ade..2bde53d 100644 --- a/lib-mail/build.gradle +++ b/lib-mail/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ @@ -26,9 +26,9 @@ plugins { dependencies { compileOnly "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" implementation "ch.qos.logback:logback-classic:1.5.25" - implementation "org.apache.groovy:groovy:4.0.24" - implementation "org.apache.groovy:groovy-nio:4.0.24" - implementation "org.apache.groovy:groovy-templates:4.0.24" + implementation "org.apache.groovy:groovy:4.0.31" + implementation "org.apache.groovy:groovy-nio:4.0.31" + implementation "org.apache.groovy:groovy-templates:4.0.31" api "com.sun.mail:javax.mail:1.6.2" api "org.jsoup:jsoup:1.15.3" implementation("io.micronaut:micronaut-runtime:${micronautCoreVersion}") diff --git a/lib-node-id-redis/build.gradle b/lib-node-id-redis/build.gradle index ea0428c..eeadfb1 100644 --- a/lib-node-id-redis/build.gradle +++ b/lib-node-id-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id "io.micronaut.minimal.library" version '4.5.3' } @@ -31,7 +31,7 @@ dependencies { compileOnly "io.micronaut:micronaut-inject:${micronautCoreVersion}" implementation "org.slf4j:slf4j-api:2.0.12" - testImplementation "org.apache.groovy:groovy:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" testImplementation "io.micronaut.test:micronaut-test-spock:${micronautTestVersion}" testImplementation testFixtures(project(':lib-fixtures-redis')) diff --git a/lib-pairing/build.gradle b/lib-pairing/build.gradle index c138829..6c27e7c 100644 --- a/lib-pairing/build.gradle +++ b/lib-pairing/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ diff --git a/lib-platform-oidc/build.gradle b/lib-platform-oidc/build.gradle index 8bd10dd..01c8064 100644 --- a/lib-platform-oidc/build.gradle +++ b/lib-platform-oidc/build.gradle @@ -23,5 +23,5 @@ group = 'io.seqera' version = "${project.file('VERSION').text.trim()}" dependencies { - testImplementation "org.apache.groovy:groovy:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" } diff --git a/lib-util-http/build.gradle b/lib-util-http/build.gradle index d2fd786..682459c 100644 --- a/lib-util-http/build.gradle +++ b/lib-util-http/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ id "io.micronaut.minimal.library" version '4.5.3' diff --git a/lib-util-tsid/build.gradle b/lib-util-tsid/build.gradle index 9603498..aa1b54d 100644 --- a/lib-util-tsid/build.gradle +++ b/lib-util-tsid/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' id "io.micronaut.minimal.library" version '4.5.3' } @@ -30,7 +30,7 @@ dependencies { compileOnly "io.micronaut:micronaut-inject:${micronautCoreVersion}" implementation "org.slf4j:slf4j-api:2.0.12" - testImplementation "org.apache.groovy:groovy:4.0.24" + testImplementation "org.apache.groovy:groovy:4.0.31" testImplementation "io.micronaut:micronaut-inject-groovy:${micronautCoreVersion}" testImplementation "io.micronaut.test:micronaut-test-spock:${micronautTestVersion}" } diff --git a/micronaut-cache-redis/build.gradle b/micronaut-cache-redis/build.gradle index c59c862..7705d51 100644 --- a/micronaut-cache-redis/build.gradle +++ b/micronaut-cache-redis/build.gradle @@ -16,7 +16,7 @@ */ plugins { - id 'io.seqera.java-library-conventions' + id 'io.seqera.micronaut-library-conventions' id 'io.seqera.groovy-library-conventions' // Micronaut minimal lib // https://micronaut-projects.github.io/micronaut-gradle-plugin/latest/ From e04d78d31b6c3383616493289f2ebe65d563fd8b Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Fri, 24 Jul 2026 17:54:09 +0200 Subject: [PATCH 4/4] docs: fix stale executor docs; add foojay toolchain resolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - settings.gradle: add foojay-resolver-convention so the JDK 25 toolchain is auto-provisioned on machines without a local JDK 25 - AbstractWorkQueue/AbstractMessageStream javadoc: usage example now shows the mandatory withHandlerExecutor() call before addConsumer() - workqueue/stream READMEs: drop the stale 'virtual-thread executor by default' wording — the handler executor is injected, no default Co-Authored-By: Claude Fable 5 --- lib-data-stream-redis/README.md | 8 ++++---- .../java/io/seqera/data/stream/AbstractMessageStream.java | 3 +++ lib-data-workqueue/README.md | 8 ++++---- .../java/io/seqera/data/workqueue/AbstractWorkQueue.java | 3 +++ settings.gradle | 5 +++++ 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib-data-stream-redis/README.md b/lib-data-stream-redis/README.md index cfe7689..9caab6b 100644 --- a/lib-data-stream-redis/README.md +++ b/lib-data-stream-redis/README.md @@ -173,7 +173,7 @@ finishes or the consumer dies. │ (PEL, │ renew (XCLAIM … JUSTID) │ • hand it to the executor │ │ group) │◀───────────── heartbeat daemon ───┤ (never runs it inline) │ │ │ every claim-timeout/3 │ │ - │ │ ack (XACK + XDEL) │ worker (virtual thread) │ + │ │ ack (XACK + XDEL) │ worker (executor thread) │ │ │◀──────────── on terminal ─────────┤ accept(msg): │ └──────────┘ │ ├─ true → ack + free slot │ ▲ │ └─ false → keep lease, │ @@ -185,9 +185,9 @@ finishes or the consumer dies. **Three mechanisms:** 1. **Async dispatch (no head-of-line blocking).** The dispatcher thread never runs a - handler; it hands each message to a worker executor and moves on. Handlers run on a - shared **virtual-thread** executor by default, or the Micronaut `@Named(BLOCKING)` - executor when supplied via `withHandlerExecutor(...)`. A `Semaphore` sized by + handler; it hands each message to a worker executor and moves on. Handlers run on the + executor supplied via `withHandlerExecutor(...)` — **mandatory, no default** (Micronaut + consumers inject the `@Named(BLOCKING)` executor). A `Semaphore` sized by `concurrency()` bounds how many messages are in flight at once (backpressure: excess messages stay in the stream). diff --git a/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java b/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java index a0e2ab8..140ac34 100644 --- a/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java +++ b/lib-data-stream-redis/src/main/java/io/seqera/data/stream/AbstractMessageStream.java @@ -78,6 +78,9 @@ * // Usage * MyMessageStream stream = new MyMessageStream(underlyingStream); * + * // Supply the handler executor (mandatory, no default) before adding consumers + * stream.withHandlerExecutor(executorService); + * * // Add consumer for a specific stream * stream.addConsumer("user-events", event -> { * processUserEvent(event); diff --git a/lib-data-workqueue/README.md b/lib-data-workqueue/README.md index 289aed0..8a1abc7 100644 --- a/lib-data-workqueue/README.md +++ b/lib-data-workqueue/README.md @@ -170,7 +170,7 @@ finishes or the consumer dies. │ (PEL, │ renewLease (XCLAIM … JUSTID) │ • hand it to the executor │ │ group) │◀──────────── heartbeat daemon ────┤ (never runs it inline) │ │ │ every visibility-timeout/3 │ │ - │ │ ack (XACK + XDEL) │ worker (virtual thread) │ + │ │ ack (XACK + XDEL) │ worker (executor thread) │ │ │◀──────────── on terminal ─────────┤ accept(msg): │ └──────────┘ │ ├─ true → ack + free slot │ ▲ │ └─ false → keep lease, │ @@ -182,9 +182,9 @@ finishes or the consumer dies. **Three mechanisms:** 1. **Async dispatch (no head-of-line blocking).** The dispatcher thread never runs a - handler; it hands each message to a worker executor and moves on. Handlers run on a - shared **virtual-thread** executor by default, or the Micronaut `@Named(BLOCKING)` - executor when supplied via `withHandlerExecutor(...)`. A `Semaphore` sized by + handler; it hands each message to a worker executor and moves on. Handlers run on the + executor supplied via `withHandlerExecutor(...)` — **mandatory, no default** (Micronaut + consumers inject the `@Named(BLOCKING)` executor). A `Semaphore` sized by `concurrency()` bounds how many messages are in flight at once (backpressure: excess messages stay in the queue). diff --git a/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java b/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java index cc4998e..760f0fc 100644 --- a/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java +++ b/lib-data-workqueue/src/main/java/io/seqera/data/workqueue/AbstractWorkQueue.java @@ -78,6 +78,9 @@ * // Usage * MyWorkQueue queue = new MyWorkQueue(underlyingQueue); * + * // Supply the handler executor (mandatory, no default) before adding consumers + * queue.withHandlerExecutor(executorService); + * * // Add consumer for a specific queue * queue.addConsumer("user-events", event -> { * processUserEvent(event); diff --git a/settings.gradle b/settings.gradle index e790f27..34278a4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,11 @@ pluginManagement { } } +plugins { + // auto-provisions the JDK required by the java toolchain when not installed locally + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + rootProject.name = 'libseqera' include('lib-activator')