From 83d4909ad52106694c08d71681d6e1bbe96d2ca7 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Mon, 26 Jan 2026 17:40:17 +0100 Subject: [PATCH] New executor defaults for handler runner --- .../java/dev/restate/sdk/HandlerRunner.java | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java b/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java index fbb1fcbf..538a6ea8 100644 --- a/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java +++ b/sdk-api/src/main/java/dev/restate/sdk/HandlerRunner.java @@ -9,10 +9,7 @@ package dev.restate.sdk; import dev.restate.common.Slice; -import dev.restate.common.function.ThrowingBiConsumer; -import dev.restate.common.function.ThrowingBiFunction; -import dev.restate.common.function.ThrowingConsumer; -import dev.restate.common.function.ThrowingFunction; +import dev.restate.common.function.*; import dev.restate.sdk.common.TerminalException; import dev.restate.sdk.endpoint.definition.HandlerContext; import dev.restate.sdk.internal.ContextThreadLocal; @@ -22,6 +19,7 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.LogManager; @@ -182,25 +180,43 @@ public static HandlerRunner of( public static final class Options implements dev.restate.sdk.endpoint.definition.HandlerRunner.Options { /** - * Default options will use a {@link Executors#newCachedThreadPool()} shared among all the - * {@link HandlerRunner} instances. + * Default options will use virtual threads on Java 21+, or fallback to {@link + * Executors#newCachedThreadPool()} for Java < 21. The bounded pool is shared among all + * {@link HandlerRunner} instances, and is used by {@link Restate#run}/{@link Context#run} as + * well. */ - public static final Options DEFAULT = new Options(Executors.newCachedThreadPool()); + public static final Options DEFAULT = new Options(createDefaultExecutor()); private final Executor executor; - /** - * You can run on virtual threads by using the executor {@code - * Executors.newVirtualThreadPerTaskExecutor()}. - */ private Options(Executor executor) { this.executor = executor; } - /** Copy this options setting the given {@code executor}. */ + /** + * Create an instance of {@link Options} with the given {@code executor}. + * + *

The given executor is used for running the handler code, and {@link Restate#run}/{@link + * Context#run} as well. + */ public static Options withExecutor(Executor executor) { return new Options(executor); } + + private static ExecutorService createDefaultExecutor() { + // Try to use virtual threads if available (Java 21+) + try { + return (ExecutorService) + Executors.class.getMethod("newVirtualThreadPerTaskExecutor").invoke(null); + } catch (Exception e) { + LOG.debug( + "Virtual threads not available, using unbounded thread pool. " + + "If you need to customize the thread pool used by your restate handlers, " + + "use HandlerRunner.Options.withExecutor() with Endpoint.bind()"); + + return Executors.newCachedThreadPool(); + } + } } static HandlerContext getHandlerContext() {