Upgrading workflow code from a release that still had `durabletask-client`? See
+> [MIGRATION.md](MIGRATION.md).
+
+- **`io.dapr:dapr-sdk-bom`** — core SDK modules (`dapr-sdk`, `dapr-sdk-actors`, `dapr-sdk-workflows`, `dapr-sdk-autogen`, `testcontainers-dapr`) plus security-patched transitive dependencies (Netty, Jackson, commons-compress, commons-codec).
- **`io.dapr.spring:dapr-spring-bom`** — Spring-specific modules (`dapr-sdk-springboot`, `dapr-spring-*`). Imports `dapr-sdk-bom` transitively, so Spring users only need this single BOM.
Pick the one that matches your project. Importing a BOM ensures you inherit security fixes for transitive dependencies like the Netty CVEs.
diff --git a/SUPPORT.md b/SUPPORT.md
index eeb585d6c6..eb10686a2a 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -12,7 +12,46 @@ at a given version pins the whole stack, core and Spring, to that single version
| **1.19.x+** | v1.18 | 4.0.x | 17+ | Active — new features and fixes |
| **1.18.x** | v1.18 | 3.5.x | 17+ | Maintenance — best-effort security/critical fixes |
-The **core** SDK modules (`dapr-sdk`, `dapr-sdk-actors`, `dapr-sdk-workflows`, `durabletask-client`)
+### Migrating to the unified workflows module
+
+In **1.19.x**, `durabletask-client` has been folded into `dapr-sdk-workflows`, which is a breaking
+change for workflow code — so upgrading from 1.18.x requires source changes. See
+[MIGRATION.md](MIGRATION.md) for the type mapping, the removed API, and what it means for workflows
+that are already running when you upgrade.
+
+### Java 21 is recommended for workflows
+
+`dapr-sdk-workflows` is compiled for Java 17 and runs on Java 17 or later. **Java 21 or later is
+recommended.**
+
+The workflow runtime executes workflows and activities on an `ExecutorService`. On Java 21+ the
+default is a virtual-thread-per-task executor; on Java 17 through 20 it is a cached thread pool.
+You can always supply your own with `WorkflowRuntimeBuilder.withExecutorService(...)`, and an
+executor you supply is never shut down by the runtime.
+
+Virtual threads are on by default there. To opt out and keep the cached thread pool, set the JVM
+system property `dapr.workflows.virtual.threads.enabled=false` or the environment variable
+`DAPR_WORKFLOWS_VIRTUAL_THREADS_ENABLED=false`. The setting only affects the executor the runtime
+creates for itself; it has no effect on Java 17 through 20, or on a runtime given an executor via
+`withExecutorService(...)`.
+
+Note for Spring Boot users: the auto-configured `WorkflowRuntimeBuilder` also reads
+`dapr.workflows.virtual.threads.enabled` from the Spring `Environment`, so you can set it in
+`application.properties` alongside your other Dapr settings. `spring.threads.virtual.enabled` is a
+separate switch and does **not** reach the workflow runtime: it changes Spring's own executors, and
+the auto-configuration does not hand the application task executor to `WorkflowRuntimeBuilder`. You
+do not need it, because workflows already run on virtual threads by default on Java 21. To put
+workflows on an executor you control, define your own `WorkflowRuntimeBuilder` bean and call
+`withExecutorService(...)`.
+
+Virtual threads help **activities**, which run your code and typically block on I/O. Workflow code
+itself is replay-based and does not block, so it sees little benefit. The SDK holds no monitor on
+the work-item dispatch path and will not pin a carrier thread, but your own activity code can: a
+`synchronized` block around a blocking call will pin on Java 21 through 23. Note also that a
+virtual-thread-per-task executor is unbounded — if your activities hit a bounded resource such as
+a JDBC pool or a rate-limited API, size that resource or supply a bounded executor instead.
+
+The **core** SDK modules (`dapr-sdk`, `dapr-sdk-actors`, `dapr-sdk-workflows`)
are framework-agnostic and do not depend on Spring Boot — the Spring Boot column applies only to the
Spring integration modules (`dapr-sdk-springboot`, `dapr-spring-*`). Core and Spring modules always
share the same SDK version (the Spring BOM imports `dapr-sdk-bom` at its own version), so they never
diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java
index 4fd6f0f889..b0a8c5811d 100644
--- a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java
+++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java
@@ -33,6 +33,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.Environment;
import java.util.HashMap;
import java.util.Map;
@@ -132,8 +133,9 @@ ActorRuntime daprActorRuntime(DaprConnectionDetails daprConnectionDetails) {
@Bean
@ConditionalOnMissingBean
- WorkflowRuntimeBuilder daprWorkflowRuntimeBuilder(DaprConnectionDetails daprConnectionDetails) {
- Properties properties = createPropertiesFromConnectionDetails(daprConnectionDetails);
+ WorkflowRuntimeBuilder daprWorkflowRuntimeBuilder(DaprConnectionDetails daprConnectionDetails,
+ Environment environment) {
+ Properties properties = createWorkflowProperties(daprConnectionDetails, environment);
return new WorkflowRuntimeBuilder(properties);
}
@@ -152,6 +154,36 @@ protected DaprClientBuilder createDaprClientBuilder() {
* @return the Properties object
*/
protected Properties createPropertiesFromConnectionDetails(DaprConnectionDetails daprConnectionDetails) {
+ return new Properties(createPropertyOverrides(daprConnectionDetails));
+ }
+
+ /**
+ * Creates a Properties object for the workflow runtime, layering the Spring Environment on top of
+ * the connection details.
+ *
+ * The runtime resolves {@link Properties#WORKFLOWS_VIRTUAL_THREADS_ENABLED} through the SDK
+ * Properties, which consults only JVM system properties and environment variables. Reading it
+ * from the Environment here lets it be set in application.properties like any other Spring
+ * property. When it is set nowhere the override is omitted, so the SDK default still applies.
+ *
+ * @param daprConnectionDetails the DaprConnectionDetails
+ * @param environment the Spring Environment
+ * @return the Properties object
+ */
+ protected Properties createWorkflowProperties(DaprConnectionDetails daprConnectionDetails,
+ Environment environment) {
+ Map propertyOverrides = createPropertyOverrides(daprConnectionDetails);
+ String propertyName = Properties.WORKFLOWS_VIRTUAL_THREADS_ENABLED.getName();
+ String virtualThreadsEnabled = environment.getProperty(propertyName);
+
+ if (virtualThreadsEnabled != null) {
+ propertyOverrides.put(propertyName, virtualThreadsEnabled);
+ }
+
+ return new Properties(propertyOverrides);
+ }
+
+ private Map createPropertyOverrides(DaprConnectionDetails daprConnectionDetails) {
Map propertyOverrides = new HashMap<>();
String httpEndpoint = daprConnectionDetails.getHttpEndpoint();
@@ -182,7 +214,7 @@ protected Properties createPropertiesFromConnectionDetails(DaprConnectionDetails
propertyOverrides.put(Properties.API_TOKEN.getName(), apiToken);
}
- return new Properties(propertyOverrides);
+ return propertyOverrides;
}
}
diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java
index 4eae48bf30..ad2c12a592 100644
--- a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java
+++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfigurationTest.java
@@ -27,6 +27,7 @@
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
@@ -187,6 +188,37 @@ void shouldOverrideApiTokenPropertiesIfExists() {
assertThat(result.getValue(Properties.API_TOKEN)).isEqualTo(apiToken);
}
+ @Test
+ @DisplayName("Should read the virtual threads property from the Spring Environment")
+ void shouldReadVirtualThreadsPropertyFromEnvironment() {
+ MockEnvironment environment = new MockEnvironment()
+ .withProperty(Properties.WORKFLOWS_VIRTUAL_THREADS_ENABLED.getName(), "false");
+
+ Properties result = configuration.createWorkflowProperties(connectionDetails, environment);
+
+ assertThat(result.getValue(Properties.WORKFLOWS_VIRTUAL_THREADS_ENABLED)).isFalse();
+ }
+
+ @Test
+ @DisplayName("Should keep the virtual threads default when the Environment does not set it")
+ void shouldKeepVirtualThreadsDefaultWhenEnvironmentDoesNotSetIt() {
+ Properties result = configuration.createWorkflowProperties(connectionDetails, new MockEnvironment());
+
+ assertThat(result.getValue(Properties.WORKFLOWS_VIRTUAL_THREADS_ENABLED)).isTrue();
+ }
+
+ @Test
+ @DisplayName("Should keep the connection details in the workflow properties")
+ void shouldKeepConnectionDetailsInWorkflowProperties() {
+ String grpcEndpoint = "grpc://localhost:5001";
+
+ when(connectionDetails.getGrpcEndpoint()).thenReturn(grpcEndpoint);
+
+ Properties result = configuration.createWorkflowProperties(connectionDetails, new MockEnvironment());
+
+ assertThat(result.getValue(Properties.GRPC_ENDPOINT)).isEqualTo(grpcEndpoint);
+ }
+
private static class TestDaprClientAutoConfiguration extends DaprClientAutoConfiguration {
private final DaprClientBuilder daprClientBuilder;
diff --git a/dapr-spring/dapr-spring-boot-observation/src/main/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClient.java b/dapr-spring/dapr-spring-boot-observation/src/main/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClient.java
index 4366857e75..86fdd19fee 100644
--- a/dapr-spring/dapr-spring-boot-observation/src/main/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClient.java
+++ b/dapr-spring/dapr-spring-boot-observation/src/main/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClient.java
@@ -39,11 +39,10 @@
/**
* A {@link DaprWorkflowClient} subclass that creates Micrometer Observation spans (bridged to
- * OpenTelemetry) for each non-deprecated method call.
+ * OpenTelemetry) for each method call.
*
* Because this class extends {@link DaprWorkflowClient}, consumers can keep injecting
- * {@code DaprWorkflowClient} without any code changes. Deprecated methods fall through to the
- * parent implementation without any observation.
+ * {@code DaprWorkflowClient} without any code changes.
*
*
Trace propagation: an {@link OtelTracingClientInterceptor} is registered on the gRPC
* channel. For each synchronous workflow RPC, the observation opens an OTel scope (via
@@ -334,10 +333,6 @@ public boolean purgeWorkflow(String workflowInstanceId) {
}
}
- // Deprecated methods (getInstanceState, waitForInstanceStart, waitForInstanceCompletion,
- // purgeInstance) are intentionally not overridden — they fall through to the parent
- // implementation without any observation.
-
// -------------------------------------------------------------------------
// gRPC interceptor: injects the current OTel span's traceparent into headers
// -------------------------------------------------------------------------
diff --git a/dapr-spring/dapr-spring-boot-observation/src/test/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClientTest.java b/dapr-spring/dapr-spring-boot-observation/src/test/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClientTest.java
index ede8f49ce8..b2c2086ff8 100644
--- a/dapr-spring/dapr-spring-boot-observation/src/test/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClientTest.java
+++ b/dapr-spring/dapr-spring-boot-observation/src/test/java/io/dapr/spring/observation/client/ObservationDaprWorkflowClientTest.java
@@ -202,22 +202,6 @@ void purgeWorkflowCreatesSpan() {
.hasError();
}
- // -------------------------------------------------------------------------
- // Deprecated methods — must NOT create spans
- // -------------------------------------------------------------------------
-
- @Test
- @DisplayName("Deprecated getInstanceState falls through to parent without creating a span")
- @SuppressWarnings("deprecation")
- void deprecatedGetInstanceStateDoesNotCreateSpan() {
- // This will fail (no sidecar) but must not leave any observations in the registry
- assertThatThrownBy(() -> client.getInstanceState("instance-1", false))
- .isInstanceOf(RuntimeException.class);
-
- // No spans should have been created for deprecated methods
- TestObservationRegistryAssert.assertThat(registry).doesNotHaveAnyObservation();
- }
-
// -------------------------------------------------------------------------
// Dummy workflow implementation for type-based tests
// -------------------------------------------------------------------------
diff --git a/durabletask-client/pom.xml b/durabletask-client/pom.xml
deleted file mode 100644
index 9d0e4e8aea..0000000000
--- a/durabletask-client/pom.xml
+++ /dev/null
@@ -1,240 +0,0 @@
-
-
- 4.0.0
-
- io.dapr
- dapr-sdk-parent
- 1.19.0-SNAPSHOT
- ../pom.xml
-
-
- durabletask-client
- jar
- durabletask-client
- Durable Task Client for Dapr Workflows
-
-
- false
- ${project.build.directory}/generated-sources
- ${project.build.directory}/proto
-
-
-
-
- javax.annotation
- javax.annotation-api
- provided
-
-
- io.grpc
- grpc-protobuf
-
-
- io.grpc
- grpc-stub
-
-
- io.grpc
- grpc-netty
-
-
- com.google.protobuf
- protobuf-java
-
-
- com.fasterxml.jackson.core
- jackson-core
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
- com.fasterxml.jackson.core
- jackson-annotations
-
-
- com.fasterxml.jackson.datatype
- jackson-datatype-jsr310
-
-
- org.apache.commons
- commons-lang3
-
-
- io.grpc
- grpc-testing
- test
-
-
- org.junit.jupiter
- junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers
- test
-
-
- io.micrometer
- micrometer-observation
-
-
- io.opentelemetry
- opentelemetry-api
-
-
- io.opentelemetry
- opentelemetry-context
-
-
- io.opentelemetry
- opentelemetry-sdk
- test
-
-
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
-
-
- org.apache.maven.plugins
- maven-failsafe-plugin
-
- ${project.build.outputDirectory}
-
-
-
- com.googlecode.maven-download-plugin
- download-maven-plugin
- ${download-maven-plugin.version}
-
-
- getOrchestratorServiceProto
- initialize
-
- wget
-
-
- true
- ${durabletask.proto.baseurl}/orchestrator_service.proto
- orchestrator_service.proto
- ${protobuf.input.directory}
-
-
-
- getOrchestrationProto
- initialize
-
- wget
-
-
- true
- ${durabletask.proto.baseurl}/orchestration.proto
- orchestration.proto
- ${protobuf.input.directory}
-
-
-
- getHistoryEventsProto
- initialize
-
- wget
-
-
- true
- ${durabletask.proto.baseurl}/history_events.proto
- history_events.proto
- ${protobuf.input.directory}
-
-
-
- getOrchestratorActionsProto
- initialize
-
- wget
-
-
- true
- ${durabletask.proto.baseurl}/orchestrator_actions.proto
- orchestrator_actions.proto
- ${protobuf.input.directory}
-
-
-
- getAttestationProto
- initialize
-
- wget
-
-
- true
- ${durabletask.proto.baseurl}/attestation.proto
- attestation.proto
- ${protobuf.input.directory}
-
-
-
-
-
- org.xolstice.maven.plugins
- protobuf-maven-plugin
- ${protobuf-maven-plugin.version}
-
- com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
- grpc-java
- io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
- ${protobuf.input.directory}
-
-
-
-
- compile
- compile-custom
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
-
- jar-no-fork
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
- true
-
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- com.github.spotbugs
- spotbugs-maven-plugin
-
-
- true
-
-
-
-
-
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/Helpers.java b/durabletask-client/src/main/java/io/dapr/durabletask/Helpers.java
deleted file mode 100644
index 265bb0ab06..0000000000
--- a/durabletask-client/src/main/java/io/dapr/durabletask/Helpers.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2025 The Dapr Authors
- * 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.dapr.durabletask;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.time.Duration;
-
-final class Helpers {
- static final Duration maxDuration = Duration.ofSeconds(Long.MAX_VALUE, 999999999L);
-
- static @Nonnull V throwIfArgumentNull(@Nullable V argValue, String argName) {
- if (argValue == null) {
- throw new IllegalArgumentException("The argument '" + argName + "' was null.");
- }
-
- return argValue;
- }
-
- static @Nonnull String throwIfArgumentNullOrWhiteSpace(String argValue, String argName) {
- throwIfArgumentNull(argValue, argName);
- if (argValue.trim().length() == 0) {
- throw new IllegalArgumentException("The argument '" + argName + "' was empty or contained only whitespace.");
- }
-
- return argValue;
- }
-
- static void throwIfOrchestratorComplete(boolean isComplete) {
- if (isComplete) {
- throw new IllegalStateException("The orchestrator has already completed");
- }
- }
-
- static boolean isInfiniteTimeout(Duration timeout) {
- return timeout == null || timeout.isNegative() || timeout.equals(maxDuration);
- }
-
- static double powExact(double base, double exponent) throws ArithmeticException {
- if (base == 0.0) {
- return 0.0;
- }
-
- double result = Math.pow(base, exponent);
-
- if (result == Double.POSITIVE_INFINITY) {
- throw new ArithmeticException("Double overflow resulting in POSITIVE_INFINITY");
- } else if (result == Double.NEGATIVE_INFINITY) {
- throw new ArithmeticException("Double overflow resulting in NEGATIVE_INFINITY");
- } else if (Double.compare(-0.0f, result) == 0) {
- throw new ArithmeticException("Double overflow resulting in negative zero");
- } else if (Double.compare(+0.0f, result) == 0) {
- throw new ArithmeticException("Double overflow resulting in positive zero");
- }
-
- return result;
- }
-
- static boolean isNullOrEmpty(String s) {
- return s == null || s.isEmpty();
- }
-
- // Cannot be instantiated
- private Helpers() {
- }
-}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/OrchestrationRuntimeStatus.java b/durabletask-client/src/main/java/io/dapr/durabletask/OrchestrationRuntimeStatus.java
deleted file mode 100644
index 3dde08ccb9..0000000000
--- a/durabletask-client/src/main/java/io/dapr/durabletask/OrchestrationRuntimeStatus.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2025 The Dapr Authors
- * 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.dapr.durabletask;
-
-import io.dapr.durabletask.implementation.protobuf.Orchestration;
-
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_CANCELED;
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_COMPLETED;
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_CONTINUED_AS_NEW;
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED;
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_PENDING;
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_RUNNING;
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_SUSPENDED;
-import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_TERMINATED;
-
-/**
- * Enum describing the runtime status of the orchestration.
- */
-public enum OrchestrationRuntimeStatus {
- /**
- * The orchestration started running.
- */
- RUNNING,
-
- /**
- * The orchestration completed normally.
- */
- COMPLETED,
-
- /**
- * The orchestration is transitioning into a new instance.
- * This status value is obsolete and exists only for compatibility reasons.
- */
- CONTINUED_AS_NEW,
-
- /**
- * The orchestration completed with an unhandled exception.
- */
- FAILED,
-
- /**
- * The orchestration canceled gracefully.
- * The Canceled status is not currently used and exists only for compatibility reasons.
- */
- CANCELED,
-
- /**
- * The orchestration was abruptly terminated via a management API call.
- */
- TERMINATED,
-
- /**
- * The orchestration was scheduled but hasn't started running.
- */
- PENDING,
-
- /**
- * The orchestration is in a suspended state.
- */
- SUSPENDED,
-
- /**
- * The orchestration is in a stalled state.
- */
- STALLED;
-
- static OrchestrationRuntimeStatus fromProtobuf(Orchestration.OrchestrationStatus status) {
- switch (status) {
- case ORCHESTRATION_STATUS_RUNNING:
- return RUNNING;
- case ORCHESTRATION_STATUS_COMPLETED:
- return COMPLETED;
- case ORCHESTRATION_STATUS_CONTINUED_AS_NEW:
- return CONTINUED_AS_NEW;
- case ORCHESTRATION_STATUS_FAILED:
- return FAILED;
- case ORCHESTRATION_STATUS_CANCELED:
- return CANCELED;
- case ORCHESTRATION_STATUS_TERMINATED:
- return TERMINATED;
- case ORCHESTRATION_STATUS_PENDING:
- return PENDING;
- case ORCHESTRATION_STATUS_SUSPENDED:
- return SUSPENDED;
- case ORCHESTRATION_STATUS_STALLED:
- return STALLED;
- default:
- throw new IllegalArgumentException(String.format("Unknown status value: %s", status));
- }
- }
-
- static Orchestration.OrchestrationStatus toProtobuf(OrchestrationRuntimeStatus status) {
- switch (status) {
- case RUNNING:
- return ORCHESTRATION_STATUS_RUNNING;
- case COMPLETED:
- return ORCHESTRATION_STATUS_COMPLETED;
- case CONTINUED_AS_NEW:
- return ORCHESTRATION_STATUS_CONTINUED_AS_NEW;
- case FAILED:
- return ORCHESTRATION_STATUS_FAILED;
- case CANCELED:
- return ORCHESTRATION_STATUS_CANCELED;
- case TERMINATED:
- return ORCHESTRATION_STATUS_TERMINATED;
- case PENDING:
- return ORCHESTRATION_STATUS_PENDING;
- case SUSPENDED:
- return ORCHESTRATION_STATUS_SUSPENDED;
- default:
- throw new IllegalArgumentException(String.format("Unknown status value: %s", status));
- }
- }
-}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/RetryContext.java b/durabletask-client/src/main/java/io/dapr/durabletask/RetryContext.java
deleted file mode 100644
index 620e02c7d3..0000000000
--- a/durabletask-client/src/main/java/io/dapr/durabletask/RetryContext.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2025 The Dapr Authors
- * 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.dapr.durabletask;
-
-import java.time.Duration;
-
-/**
- * Context data that's provided to {@link RetryHandler} implementations.
- */
-public final class RetryContext {
- private final TaskOrchestrationContext orchestrationContext;
- private final int lastAttemptNumber;
- private final FailureDetails lastFailure;
- private final Duration totalRetryTime;
-
- RetryContext(
- TaskOrchestrationContext orchestrationContext,
- int lastAttemptNumber,
- FailureDetails lastFailure,
- Duration totalRetryTime) {
- this.orchestrationContext = orchestrationContext;
- this.lastAttemptNumber = lastAttemptNumber;
- this.lastFailure = lastFailure;
- this.totalRetryTime = totalRetryTime;
- }
-
- /**
- * Gets the context of the current orchestration.
- *
- * The orchestration context can be used in retry handlers to schedule timers (via the
- * {@link TaskOrchestrationContext#createTimer} methods) for implementing delays between retries. It can also be
- * used to implement time-based retry logic by using the {@link TaskOrchestrationContext#getCurrentInstant} method.
- *
- *
- * @return the context of the parent orchestration
- */
- public TaskOrchestrationContext getOrchestrationContext() {
- return this.orchestrationContext;
- }
-
- /**
- * Gets the details of the previous task failure, including the exception type, message, and callstack.
- *
- * @return the details of the previous task failure
- */
- public FailureDetails getLastFailure() {
- return this.lastFailure;
- }
-
- /**
- * Gets the previous retry attempt number. This number starts at 1 and increments each time the retry handler
- * is invoked for a particular task failure.
- *
- * @return the previous retry attempt number
- */
- public int getLastAttemptNumber() {
- return this.lastAttemptNumber;
- }
-
- /**
- * Gets the total amount of time spent in a retry loop for the current task.
- *
- * @return the total amount of time spent in a retry loop for the current task
- */
- public Duration getTotalRetryTime() {
- return this.totalRetryTime;
- }
-}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/RetryHandler.java b/durabletask-client/src/main/java/io/dapr/durabletask/RetryHandler.java
deleted file mode 100644
index ad246a0c65..0000000000
--- a/durabletask-client/src/main/java/io/dapr/durabletask/RetryHandler.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2025 The Dapr Authors
- * 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.dapr.durabletask;
-
-/**
- * Functional interface for implementing custom task retry handlers.
- *
- * It's important to remember that retry handler code is an extension of the orchestrator code and must
- * therefore comply with all the determinism requirements of orchestrator code.
- */
-@FunctionalInterface
-public interface RetryHandler {
- /**
- * Invokes the retry handler logic and returns a value indicating whether to continue retrying.
- *
- * @param context retry context that's updated between each retry attempt
- * @return {@code true} to continue retrying or {@code false} to stop retrying.
- */
- boolean handle(RetryContext context);
-}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/RetryPolicy.java b/durabletask-client/src/main/java/io/dapr/durabletask/RetryPolicy.java
deleted file mode 100644
index 9efd912b1e..0000000000
--- a/durabletask-client/src/main/java/io/dapr/durabletask/RetryPolicy.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2025 The Dapr Authors
- * 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.dapr.durabletask;
-
-import javax.annotation.Nullable;
-import java.time.Duration;
-import java.util.Objects;
-
-/**
- * A declarative retry policy that can be configured for activity or sub-orchestration calls.
- */
-public final class RetryPolicy {
-
- private int maxNumberOfAttempts;
- private Duration firstRetryInterval;
- private double backoffCoefficient = 1.0;
- private Duration maxRetryInterval = Duration.ZERO;
- private Duration retryTimeout = Duration.ZERO;
-
- /**
- * Creates a new {@code RetryPolicy} object.
- *
- * @param maxNumberOfAttempts the maximum number of task invocation attempts; must be 1 or greater
- * @param firstRetryInterval the amount of time to delay between the first and second attempt
- * @throws IllegalArgumentException if {@code maxNumberOfAttempts} is zero or negative
- */
- public RetryPolicy(int maxNumberOfAttempts, Duration firstRetryInterval) {
- this.setMaxNumberOfAttempts(maxNumberOfAttempts);
- this.setFirstRetryInterval(firstRetryInterval);
- }
-
- /**
- * Sets the maximum number of task invocation attempts; must be 1 or greater.
- *
- * This value represents the number of times to attempt to execute the task. It does not represent
- * the maximum number of times to retry the task. This is why the number must be 1 or greater.
- *
- * @param maxNumberOfAttempts the maximum number of attempts; must be 1 or greater
- * @return this retry policy object
- * @throws IllegalArgumentException if {@code maxNumberOfAttempts} is zero or negative
- */
- public RetryPolicy setMaxNumberOfAttempts(int maxNumberOfAttempts) {
- if (maxNumberOfAttempts <= 0) {
- throw new IllegalArgumentException("The value for maxNumberOfAttempts must be greater than zero.");
- }
- this.maxNumberOfAttempts = maxNumberOfAttempts;
- return this;
- }
-
- /**
- * Sets the amount of time to delay between the first and second attempt.
- *
- * @param firstRetryInterval the amount of time to delay between the first and second attempt
- * @return this retry policy object
- * @throws IllegalArgumentException if {@code firstRetryInterval} is {@code null}, zero, or negative.
- */
- public RetryPolicy setFirstRetryInterval(Duration firstRetryInterval) {
- if (firstRetryInterval == null) {
- throw new IllegalArgumentException("firstRetryInterval cannot be null.");
- }
- if (firstRetryInterval.isZero() || firstRetryInterval.isNegative()) {
- throw new IllegalArgumentException("The value for firstRetryInterval must be greater than zero.");
- }
- this.firstRetryInterval = firstRetryInterval;
- return this;
- }
-
- /**
- * Sets the exponential backoff coefficient used to determine the delay between subsequent retries.
- * Must be 1.0 or greater.
- *
- * To avoid extremely long delays between retries, consider also specifying a maximum retry interval using the
- * {@link #setMaxRetryInterval} method.
- *
- * @param backoffCoefficient the exponential backoff coefficient
- * @return this retry policy object
- * @throws IllegalArgumentException if {@code backoffCoefficient} is less than 1.0
- */
- public RetryPolicy setBackoffCoefficient(double backoffCoefficient) {
- if (backoffCoefficient < 1.0) {
- throw new IllegalArgumentException("The value for backoffCoefficient must be greater or equal to 1.0.");
- }
- this.backoffCoefficient = backoffCoefficient;
- return this;
- }
-
- /**
- * Sets the maximum time to delay between attempts.
- *
- * It's recommended to set a maximum retry interval whenever using a backoff coefficient that's greater than the
- * default of 1.0.
- *
- * @param maxRetryInterval the maximum time to delay between attempts or {@code null} to remove the maximum retry
- * interval
- * @return this retry policy object
- */
- public RetryPolicy setMaxRetryInterval(@Nullable Duration maxRetryInterval) {
- if (maxRetryInterval != null && maxRetryInterval.compareTo(this.firstRetryInterval) < 0) {
- throw new IllegalArgumentException("The value for maxRetryInterval must be greater than or equal to the value "
- + "for firstRetryInterval.");
- }
- this.maxRetryInterval = maxRetryInterval;
- return this;
- }
-
- /**
- * Sets the overall timeout for retries, regardless of the retry count.
- *
- * @param retryTimeout the overall timeout for retries
- * @return this retry policy object
- */
- public RetryPolicy setRetryTimeout(Duration retryTimeout) {
- if (retryTimeout == null || retryTimeout.compareTo(this.firstRetryInterval) < 0) {
- throw new IllegalArgumentException("The value for retryTimeout cannot be null and must be greater than or equal "
- + "to the value for firstRetryInterval.");
- }
- this.retryTimeout = retryTimeout;
- return this;
- }
-
- /**
- * Gets the configured maximum number of task invocation attempts.
- *
- * @return the configured maximum number of task invocation attempts.
- */
- public int getMaxNumberOfAttempts() {
- return this.maxNumberOfAttempts;
- }
-
- /**
- * Gets the configured amount of time to delay between the first and second attempt.
- *
- * @return the configured amount of time to delay between the first and second attempt
- */
- public Duration getFirstRetryInterval() {
- return this.firstRetryInterval;
- }
-
- /**
- * Gets the configured exponential backoff coefficient used to determine the delay between subsequent retries.
- *
- * @return the configured exponential backoff coefficient used to determine the delay between subsequent retries
- */
- public double getBackoffCoefficient() {
- return this.backoffCoefficient;
- }
-
- /**
- * Gets the configured maximum time to delay between attempts.
- *
- * @return the configured maximum time to delay between attempts
- */
- public Duration getMaxRetryInterval() {
- return this.maxRetryInterval;
- }
-
- /**
- * Gets the configured overall timeout for retries.
- *
- * @return the configured overall timeout for retries
- */
- public Duration getRetryTimeout() {
- return this.retryTimeout;
- }
-}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOptions.java b/durabletask-client/src/main/java/io/dapr/durabletask/TaskOptions.java
deleted file mode 100644
index 0c4772626e..0000000000
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOptions.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright 2026 The Dapr Authors
- * 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.dapr.durabletask;
-
-/**
- * Options that can be used to control the behavior of orchestrator and activity task execution.
- */
-public final class TaskOptions {
- private final RetryPolicy retryPolicy;
- private final RetryHandler retryHandler;
- private final String appID;
- private final HistoryPropagationScope historyPropagationScope;
-
- private TaskOptions(RetryPolicy retryPolicy, RetryHandler retryHandler, String appID,
- HistoryPropagationScope historyPropagationScope) {
- this.retryPolicy = retryPolicy;
- this.retryHandler = retryHandler;
- this.appID = appID;
- this.historyPropagationScope = historyPropagationScope;
- }
-
- /**
- * Creates a new builder for {@code TaskOptions}.
- *
- * @return a new builder instance
- */
- public static Builder builder() {
- return new Builder();
- }
-
- /**
- * Creates a new {@code TaskOptions} object with default values.
- *
- * @return a new TaskOptions instance with no configuration
- */
- public static TaskOptions create() {
- return new Builder().build();
- }
-
- /**
- * Creates a new {@code TaskOptions} object from a {@link RetryPolicy}.
- *
- * @param retryPolicy the retry policy to use in the new {@code TaskOptions} object.
- * @return a new TaskOptions instance with the specified retry policy
- */
- public static TaskOptions withRetryPolicy(RetryPolicy retryPolicy) {
- return new Builder().retryPolicy(retryPolicy).build();
- }
-
- /**
- * Creates a new {@code TaskOptions} object from a {@link RetryHandler}.
- *
- * @param retryHandler the retry handler to use in the new {@code TaskOptions} object.
- * @return a new TaskOptions instance with the specified retry handler
- */
- public static TaskOptions withRetryHandler(RetryHandler retryHandler) {
- return new Builder().retryHandler(retryHandler).build();
- }
-
- /**
- * Creates a new {@code TaskOptions} object with the specified app ID.
- *
- * @param appID the app ID to use for cross-app workflow routing
- * @return a new TaskOptions instance with the specified app ID
- */
- public static TaskOptions withAppID(String appID) {
- return new Builder().appID(appID).build();
- }
-
- boolean hasRetryPolicy() {
- return this.retryPolicy != null;
- }
-
- /**
- * Gets the configured {@link RetryPolicy} value or {@code null} if none was configured.
- *
- * @return the configured retry policy
- */
- public RetryPolicy getRetryPolicy() {
- return this.retryPolicy;
- }
-
- boolean hasRetryHandler() {
- return this.retryHandler != null;
- }
-
- /**
- * Gets the configured {@link RetryHandler} value or {@code null} if none was configured.
- *
- * @return the configured retry handler.
- */
- public RetryHandler getRetryHandler() {
- return this.retryHandler;
- }
-
- /**
- * Gets the configured app ID value or {@code null} if none was configured.
- *
- * @return the configured app ID
- */
- public String getAppID() {
- return this.appID;
- }
-
- boolean hasAppID() {
- return this.appID != null && !this.appID.isEmpty();
- }
-
- /**
- * Gets the configured {@link HistoryPropagationScope} value or {@code null} if none was configured.
- *
- * @return the configured history propagation scope
- */
- public HistoryPropagationScope getHistoryPropagationScope() {
- return this.historyPropagationScope;
- }
-
- boolean hasHistoryPropagationScope() {
- return this.historyPropagationScope != null
- && this.historyPropagationScope != HistoryPropagationScope.NONE;
- }
-
- /**
- * Builder for creating {@code TaskOptions} instances.
- */
- public static final class Builder {
- private RetryPolicy retryPolicy;
- private RetryHandler retryHandler;
- private String appID;
- private HistoryPropagationScope historyPropagationScope;
-
- private Builder() {
- // Private constructor -enforces using TaskOptions.builder()
- }
-
- /**
- * Sets the retry policy for the task options.
- *
- * @param retryPolicy the retry policy to use
- * @return this builder instance for method chaining
- */
- public Builder retryPolicy(RetryPolicy retryPolicy) {
- this.retryPolicy = retryPolicy;
- return this;
- }
-
- /**
- * Sets the retry handler for the task options.
- *
- * @param retryHandler the retry handler to use
- * @return this builder instance for method chaining
- */
- public Builder retryHandler(RetryHandler retryHandler) {
- this.retryHandler = retryHandler;
- return this;
- }
-
- /**
- * Sets the app ID for cross-app workflow routing.
- *
- * @param appID the app ID to use
- * @return this builder instance for method chaining
- */
- public Builder appID(String appID) {
- this.appID = appID;
- return this;
- }
-
- /**
- * Sets the history propagation scope for the task.
- *
- * @param scope the propagation scope to use
- * @return this builder instance for method chaining
- */
- public Builder historyPropagationScope(HistoryPropagationScope scope) {
- this.historyPropagationScope = scope;
- return this;
- }
-
- /**
- * Builds a new {@code TaskOptions} instance with the configured values.
- *
- * @return a new TaskOptions instance
- */
- public TaskOptions build() {
- return new TaskOptions(this.retryPolicy, this.retryHandler, this.appID,
- this.historyPropagationScope);
- }
- }
-}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestrationContext.java b/durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestrationContext.java
deleted file mode 100644
index 5efe04258f..0000000000
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestrationContext.java
+++ /dev/null
@@ -1,641 +0,0 @@
-/*
- * Copyright 2026 The Dapr Authors
- * 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.dapr.durabletask;
-
-import javax.annotation.Nullable;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.ZonedDateTime;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-/**
- * Used by orchestrators to perform actions such as scheduling tasks, durable timers, waiting for external events,
- * and for getting basic information about the current orchestration.
- */
-public interface TaskOrchestrationContext {
- /**
- * Gets the name of the current task orchestration.
- *
- * @return the name of the current task orchestration
- */
- String getName();
-
- /**
- * Gets the deserialized input of the current task orchestration.
- *
- * @param targetType the {@link Class} object associated with {@code V}
- * @param the expected type of the orchestrator input
- * @return the deserialized input as an object of type {@code V} or {@code null} if no input was provided.
- */
- V getInput(Class targetType);
-
- /**
- * Gets the unique ID of the current orchestration instance.
- *
- * @return the unique ID of the current orchestration instance
- */
- String getInstanceId();
-
- /**
- * Gets the app ID of the current orchestration instance, if available.
- * This is used for cross-app workflow routing.
- *
- * @return the app ID of the current orchestration instance, or null if not available
- */
- String getAppId();
-
- /**
- * Gets the current orchestration time in UTC.
- *
- * @return the current orchestration time in UTC
- */
- Instant getCurrentInstant();
-
- /**
- * Gets a value indicating whether the orchestrator is currently replaying a previous execution.
- *
- * Orchestrator functions are "replayed" after being unloaded from memory to reconstruct local variable state.
- * During a replay, previously executed tasks will be completed automatically with previously seen values
- * that are stored in the orchestration history. One the orchestrator reaches the point in the orchestrator
- * where it's no longer replaying existing history, this method will return {@code false}.
- *
- * You can use this method if you have logic that needs to run only when not replaying. For example,
- * certain types of application logging may become too noisy when duplicated as part of replay. The
- * application code could check to see whether the function is being replayed and then issue the log statements
- * when this value is {@code false}.
- *
- * @return {@code true} if the orchestrator is replaying, otherwise {@code false}
- */
- boolean getIsReplaying();
-
- /**
- * Returns a new {@code Task} that is completed when all tasks in {@code tasks} completes.
- * See {@link #allOf(Task[])} for more detailed information.
- *
- * @param tasks the list of {@code Task} objects
- * @param the return type of the {@code Task} objects
- * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
- * @see #allOf(Task[])
- */
- Task> allOf(List> tasks);
-
- // TODO: Update the description of allOf to be more specific about the exception behavior.
-
- // https://github.io.dapr.durabletask-java/issues/54
-
- /**
- * Returns a new {@code Task} that is completed when all the given {@code Task}s complete. If any of the given
- * {@code Task}s complete with an exception, the returned {@code Task} will also complete with
- * an {@link CompositeTaskFailedException} containing details of the first encountered failure.
- * The value of the returned {@code Task} is an ordered list of
- * the return values of the given tasks. If no tasks are provided, returns a {@code Task} completed with value
- * {@code null}.
- *
- * This method is useful for awaiting the completion of a set of independent tasks before continuing to the next
- * step in the orchestration, as in the following example:
- * {@code
- * Task t1 = ctx.callActivity("MyActivity", String.class);
- * Task t2 = ctx.callActivity("MyActivity", String.class);
- * Task t3 = ctx.callActivity("MyActivity", String.class);
- *
- * List orderedResults = ctx.allOf(t1, t2, t3).await();
- * }
- *
- * Exceptions in any of the given tasks results in an unchecked {@link CompositeTaskFailedException}.
- * This exception can be inspected to obtain failure details of individual {@link Task}s.
- * {@code
- * try {
- * List orderedResults = ctx.allOf(t1, t2, t3).await();
- * } catch (CompositeTaskFailedException e) {
- * List exceptions = e.getExceptions()
- * }
- * }
- *
- * @param tasks the {@code Task}s
- * @param the return type of the {@code Task} objects
- * @return the values of the completed {@code Task} objects in the same order as the source list
- */
- default Task> allOf(Task... tasks) {
- return this.allOf(Arrays.asList(tasks));
- }
-
- /**
- * Returns a new {@code Task} that is completed when any of the tasks in {@code tasks} completes.
- * See {@link #anyOf(Task[])} for more detailed information.
- *
- * @param tasks the list of {@code Task} objects
- * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
- * @see #anyOf(Task[])
- */
- Task> anyOf(List> tasks);
-
- /**
- * Returns a new {@code Task} that is completed when any of the given {@code Task}s complete. The value of the
- * new {@code Task} is a reference to the completed {@code Task} object. If no tasks are provided, returns a
- * {@code Task} that never completes.
- *
- * This method is useful for waiting on multiple concurrent tasks and performing a task-specific operation when the
- * first task completes, as in the following example:
- * {@code
- * Task event1 = ctx.waitForExternalEvent("Event1");
- * Task event2 = ctx.waitForExternalEvent("Event2");
- * Task event3 = ctx.waitForExternalEvent("Event3");
- *
- * Task> winner = ctx.anyOf(event1, event2, event3).await();
- * if (winner == event1) {
- * // ...
- * } else if (winner == event2) {
- * // ...
- * } else if (winner == event3) {
- * // ...
- * }
- * }
- *
- * The {@code anyOf} method can also be used for implementing long-running timeouts, as in the following example:
- *
- * {@code
- * Task activityTask = ctx.callActivity("SlowActivity");
- * Task timeoutTask = ctx.createTimer(Duration.ofMinutes(30));
- *
- * Task> winner = ctx.anyOf(activityTask, timeoutTask).await();
- * if (winner == activityTask) {
- * // completion case
- * } else {
- * // timeout case
- * }
- * }
- *
- * @param tasks the list of {@code Task} objects
- * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
- */
- default Task> anyOf(Task>... tasks) {
- return this.anyOf(Arrays.asList(tasks));
- }
-
- /**
- * Creates a durable timer that expires after the specified delay.
- *
- * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
- * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
- * it may be visible in framework logs and the stored history state.
- *
- * @param name of the timer
- * @param delay the amount of time before the timer should expire
- * @return a new {@code Task} that completes after the specified delay
- */
- Task createTimer(String name, Duration delay);
-
- /**
- * Creates a durable timer that expires after the specified delay.
- *
- * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
- * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
- * it may be visible in framework logs and the stored history state.
- *
- * @param delay the amount of time before the timer should expire
- * @return a new {@code Task} that completes after the specified delay
- */
- Task createTimer(Duration delay);
-
- /**
- * Creates a durable timer that expires after the specified timestamp with specific zone.
- *
- * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
- * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
- * it may be visible in framework logs and the stored history state.
- *
- * @param zonedDateTime timestamp with specific zone when the timer should expire
- * @return a new {@code Task} that completes after the specified delay
- */
- Task createTimer(ZonedDateTime zonedDateTime);
-
- /**
- * Creates a durable timer that expires after the specified timestamp with specific zone.
- *
- * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
- * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
- * it may be visible in framework logs and the stored history state.
- *
- * @param name for the timer
- * @param zonedDateTime timestamp with specific zone when the timer should expire
- * @return a new {@code Task} that completes after the specified delay
- */
- Task createTimer(String name, ZonedDateTime zonedDateTime);
-
- /**
- * Transitions the orchestration into the {@link OrchestrationRuntimeStatus#COMPLETED} state with the given output.
- *
- * @param output the serializable output of the completed orchestration
- */
- void complete(Object output);
-
- /**
- * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
- * that completes when the activity completes. If the activity completes successfully, the returned {@code Task}'s
- * value will be the activity's output. If the activity fails, the returned {@code Task} will complete exceptionally
- * with a {@link TaskFailedException}.
- *
- * Activities are the basic unit of work in a durable task orchestration. Unlike orchestrators, which are not
- * allowed to do any I/O or call non-deterministic APIs, activities have no implementation restrictions.
- *
- * An activity may execute in the local machine or a remote machine. The exact behavior depends on the underlying
- * storage provider, which is responsible for distributing tasks across machines. In general, you should never make
- * any assumptions about where an activity will run. You should also assume at-least-once execution guarantees for
- * activities, meaning that an activity may be executed twice if, for example, there is a process failure before
- * the activities result is saved into storage.
- *
- * Both the inputs and outputs of activities are serialized and stored in durable storage. It's highly recommended
- * to not include any sensitive data in activity inputs or outputs. It's also recommended to not use large payloads
- * for activity inputs and outputs, which can result in expensive serialization and network utilization. For data
- * that cannot be cheaply or safely persisted to storage, it's recommended to instead pass references
- * (for example, a URL to a storage blog) to the data and have activities fetch the data directly as part of their
- * implementation.
- *
- * @param name the name of the activity to call
- * @param input the serializable input to pass to the activity
- * @param options additional options that control the execution and processing of the activity
- * @param returnType the expected class type of the activity output
- * @param the expected type of the activity output
- * @return a new {@link Task} that completes when the activity completes or fails
- */
- Task callActivity(String name, Object input, TaskOptions options, Class returnType);
-
- /**
- * Asynchronously invokes an activity by name and returns a new {@link Task} that completes when the activity
- * completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a complete description.
- *
- * @param name the name of the activity to call
- * @return a new {@link Task} that completes when the activity completes or fails
- * @see #callActivity(String, Object, TaskOptions, Class)
- */
- default Task callActivity(String name) {
- return this.callActivity(name, Void.class);
- }
-
- /**
- * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
- * that completes when the activity completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a
- * complete description.
- *
- * @param name the name of the activity to call
- * @param input the serializable input to pass to the activity
- * @return a new {@link Task} that completes when the activity completes or fails
- */
- default Task callActivity(String name, Object input) {
- return this.callActivity(name, input, null, Void.class);
- }
-
- /**
- * Asynchronously invokes an activity by name and returns a new {@link Task} that completes when the activity
- * completes. If the activity completes successfully, the returned {@code Task}'s value will be the activity's
- * output. See {@link #callActivity(String, Object, TaskOptions, Class)} for a complete description.
- *
- * @param name the name of the activity to call
- * @param returnType the expected class type of the activity output
- * @param the expected type of the activity output
- * @return a new {@link Task} that completes when the activity completes or fails
- */
- default Task callActivity(String name, Class returnType) {
- return this.callActivity(name, null, null, returnType);
- }
-
- /**
- * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
- * that completes when the activity completes.If the activity completes successfully, the returned {@code Task}'s
- * value will be the activity's output. See {@link #callActivity(String, Object, TaskOptions, Class)} for a
- * complete description.
- *
- * @param name the name of the activity to call
- * @param input the serializable input to pass to the activity
- * @param returnType the expected class type of the activity output
- * @param the expected type of the activity output
- * @return a new {@link Task} that completes when the activity completes or fails
- */
- default Task callActivity(String name, Object input, Class returnType) {
- return this.callActivity(name, input, null, returnType);
- }
-
- /**
- * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
- * that completes when the activity completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a
- * complete description.
- *
- * @param name the name of the activity to call
- * @param input the serializable input to pass to the activity
- * @param options additional options that control the execution and processing of the activity
- * @return a new {@link Task} that completes when the activity completes or fails
- */
- default Task callActivity(String name, Object input, TaskOptions options) {
- return this.callActivity(name, input, options, Void.class);
- }
-
- /**
- * Restarts the orchestration with a new input and clears its history. See {@link #continueAsNew(Object, boolean)}
- * for a full description.
- *
- * @param input the serializable input data to re-initialize the instance with
- */
- default void continueAsNew(Object input) {
- this.continueAsNew(input, true);
- }
-
- /**
- * Restarts the orchestration with a new input and clears its history.
- *
- * This method is primarily designed for eternal orchestrations, which are orchestrations that
- * may not ever complete. It works by restarting the orchestration, providing it with a new input,
- * and truncating the existing orchestration history. It allows an orchestration to continue
- * running indefinitely without having its history grow unbounded. The benefits of periodically
- * truncating history include decreased memory usage, decreased storage volumes, and shorter orchestrator
- * replays when rebuilding state.
- *
- * The results of any incomplete tasks will be discarded when an orchestrator calls {@code continueAsNew}.
- * For example, if a timer is scheduled and then {@code continueAsNew} is called before the timer fires, the timer
- * event will be discarded. The only exception to this is external events. By default, if an external event is
- * received by an orchestration but not yet processed, the event is saved in the orchestration state unit it is
- * received by a call to {@link #waitForExternalEvent}. These events will remain in memory
- * even after an orchestrator restarts using {@code continueAsNew}. This behavior can be disabled by specifying
- * {@code false} for the {@code preserveUnprocessedEvents} parameter value.
- *
- * Orchestrator implementations should complete immediately after calling the{@code continueAsNew} method.
- *
- * @param input the serializable input data to re-initialize the instance with
- * @param preserveUnprocessedEvents {@code true} to push unprocessed external events into the new orchestration
- * history, otherwise {@code false}
- */
- void continueAsNew(Object input, boolean preserveUnprocessedEvents);
-
- /**
- * Check if the given patch name can be applied to the orchestration.
- *
- * @param patchName The name of the patch to check.
- * @return True if the given patch name can be applied to the orchestration, False otherwise.
- */
-
- boolean isPatched(String patchName);
-
- /**
- * Create a new Uuid that is safe for replay within an orchestration or operation.
- *
- * The default implementation of this method creates a name-based Uuid
- * using the algorithm from RFC 4122 §4.3. The name input used to generate
- * this value is a combination of the orchestration instance ID and an
- * internally managed sequence number.
- *
- *
- * @return a deterministic Uuid
- */
- default UUID newUuid() {
- throw new RuntimeException("No implementation found.");
- }
-
- /**
- * Sends an external event to another orchestration instance.
- *
- * @param instanceID the unique ID of the receiving orchestration instance.
- * @param eventName the name of the event to send
- */
- default void sendEvent(String instanceID, String eventName) {
- this.sendEvent(instanceID, eventName, null);
- }
-
- /**
- * Sends an external event to another orchestration instance.
- *
- * @param instanceId the unique ID of the receiving orchestration instance.
- * @param eventName the name of the event to send
- * @param eventData the payload of the event to send
- */
- void sendEvent(String instanceId, String eventName, Object eventData);
-
- /**
- * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
- * when the sub-orchestration completes.
- *
- * See {@link #callSubOrchestrator(String, Object, String, TaskOptions, Class)} for a full description.
- *
- * @param name the name of the orchestrator to invoke
- * @return a new {@link Task} that completes when the sub-orchestration completes or fails
- * @see #callSubOrchestrator(String, Object, String, TaskOptions, Class)
- */
- default Task callSubOrchestrator(String name) {
- return this.callSubOrchestrator(name, null);
- }
-
- /**
- * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
- * when the sub-orchestration completes.
- *
- * See {@link #callSubOrchestrator(String, Object, String, TaskOptions, Class)} for a full description.
- *
- * @param name the name of the orchestrator to invoke
- * @param input the serializable input to send to the sub-orchestration
- * @return a new {@link Task} that completes when the sub-orchestration completes or fails
- */
- default Task callSubOrchestrator(String name, Object input) {
- return this.callSubOrchestrator(name, input, Void.class);
- }
-
- /**
- * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
- * when the sub-orchestration completes.
- *
- * See {@link #callSubOrchestrator(String, Object, String, TaskOptions, Class)} for a full description.
- *
- * @param name the name of the orchestrator to invoke
- * @param input the serializable input to send to the sub-orchestration
- * @param returnType the expected class type of the sub-orchestration output
- * @param the expected type of the sub-orchestration output
- * @return a new {@link Task} that completes when the sub-orchestration completes or fails
- */
- default Task callSubOrchestrator(String name, Object input, Class returnType) {
- return this.callSubOrchestrator(name, input, null, returnType);
- }
-
- /**
- * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
- * when the sub-orchestration completes.
- *
- * See {@link #callSubOrchestrator(String, Object, String, TaskOptions, Class)} for a full description.
- *
- * @param name the name of the orchestrator to invoke
- * @param input the serializable input to send to the sub-orchestration
- * @param instanceID the unique ID of the sub-orchestration
- * @param returnType the expected class type of the sub-orchestration output
- * @param the expected type of the sub-orchestration output
- * @return a new {@link Task} that completes when the sub-orchestration completes or fails
- */
- default Task callSubOrchestrator(String name, Object input, String instanceID, Class returnType) {
- return this.callSubOrchestrator(name, input, instanceID, null, returnType);
- }
-
- /**
- * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
- * when the sub-orchestration completes.
- *
- * See {@link #callSubOrchestrator(String, Object, String, TaskOptions, Class)} for a full description.
- *
- * @param name the name of the orchestrator to invoke
- * @param input the serializable input to send to the sub-orchestration
- * @param instanceID the unique ID of the sub-orchestration
- * @param options additional options that control the execution and processing of the activity
- * @return a new {@link Task} that completes when the sub-orchestration completes or fails
- */
- default Task callSubOrchestrator(String name, Object input, String instanceID, TaskOptions options) {
- return this.callSubOrchestrator(name, input, instanceID, options, Void.class);
- }
-
- /**
- * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
- * when the sub-orchestration completes. If the sub-orchestration completes successfully, the returned
- * {@code Task}'s value will be the activity's output. If the sub-orchestration fails, the returned {@code Task}
- * will complete exceptionally with a {@link TaskFailedException}.
- *
- * A sub-orchestration has its own instance ID, history, and status that is independent of the parent orchestrator
- * that started it. There are many advantages to breaking down large orchestrations into sub-orchestrations:
- *
- * -
- * Splitting large orchestrations into a series of smaller sub-orchestrations can make code more maintainable.
- *
- * -
- * Distributing orchestration logic across multiple compute nodes concurrently is useful if
- * orchestration logic otherwise needs to coordinate a lot of tasks.
- *
- * -
- * Memory usage and CPU overhead can be reduced by keeping the history of parent orchestrations smaller.
- *
- *
- *
- * The disadvantage is that there is overhead associated with starting a sub-orchestration and processing its
- * output. This is typically only an issue for very small orchestrations.
- *
- * Because sub-orchestrations are independent of their parents, terminating a parent orchestration does not affect
- * any sub-orchestrations. Sub-orchestrations must be terminated independently using their unique instance ID,
- * which is specified using the {@code instanceID} parameter.
- *
- * @param name the name of the orchestrator to invoke
- * @param input the serializable input to send to the sub-orchestration
- * @param instanceID the unique ID of the sub-orchestration
- * @param options additional options that control the execution and processing of the activity
- * @param returnType the expected class type of the sub-orchestration output
- * @param the expected type of the sub-orchestration output
- * @return a new {@link Task} that completes when the sub-orchestration completes or fails
- */
- Task callSubOrchestrator(
- String name,
- @Nullable Object input,
- @Nullable String instanceID,
- @Nullable TaskOptions options,
- Class returnType);
-
- /**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received or is canceled when {@code timeout} expires.
- *
- * External clients can raise events to a waiting orchestration instance using the
- * {@link DurableTaskClient#raiseEvent} method.
- *
- * If the current orchestration is not yet waiting for an event named {@code name}, then the event will be saved in
- * the orchestration instance state and dispatched immediately when this method is called. This event saving occurs
- * even if the current orchestrator cancels the wait operation before the event is received.
- *
- * Orchestrators can wait for the same event name multiple times, so waiting for multiple events with the same name
- * is allowed. Each external event received by an orchestrator will complete just one task returned by this method.
- *
- *
- * @param name the case-insensitive name of the event to wait for
- * @param timeout the amount of time to wait before canceling the returned {@code Task}
- * @param dataType the expected class type of the event data payload
- * @param the expected type of the event data payload
- * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires
- * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received
- */
- Task waitForExternalEvent(String name, Duration timeout, Class dataType) throws TaskCanceledException;
-
- /**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received or is canceled when {@code timeout} expires.
- *
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
- *
- * @param name the case-insensitive name of the event to wait for
- * @param timeout the amount of time to wait before canceling the returned {@code Task}
- * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires
- * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received
- */
- default Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException {
- return this.waitForExternalEvent(name, timeout, Void.class);
- }
-
- /**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received.
- *
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
- *
- * @param name the case-insensitive name of the event to wait for
- * @return a new {@link Task} that completes when the external event is received
- */
- default Task waitForExternalEvent(String name) {
- return this.waitForExternalEvent(name, Void.class);
- }
-
- /**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received.
- *
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
- *
- * @param name the case-insensitive name of the event to wait for
- * @param dataType the expected class type of the event data payload
- * @param the expected type of the event data payload
- * @return a new {@link Task} that completes when the external event is received
- */
- default Task waitForExternalEvent(String name, Class dataType) {
- try {
- return this.waitForExternalEvent(name, null, dataType);
- } catch (TaskCanceledException e) {
- // This should never happen because of the max duration
- throw new RuntimeException("An unexpected exception was throw while waiting for an external event.", e);
- }
- }
-
- /**
- * Assigns a custom status value to the current orchestration.
- *
- * The {@code customStatus} value is serialized and stored in orchestration state and will be made available to the
- * orchestration status query APIs, such as {@link DurableTaskClient#getInstanceMetadata}. The serialized value
- * must not exceed 16 KB of UTF-16 encoded text.
- *
- * Use {@link #clearCustomStatus()} to remove the custom status value from the orchestration state.
- *
- * @param customStatus A serializable value to assign as the custom status value.
- */
- void setCustomStatus(Object customStatus);
-
- /**
- * Clears the orchestration's custom status.
- */
- void clearCustomStatus();
-
- /**
- * Gets the propagated history from a parent workflow, if any was propagated.
- *
- * @return an Optional containing the propagated history, or empty if none was propagated
- */
- Optional getPropagatedHistory();
-}
diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java
index b8ce0ef67c..2f5f00aab3 100644
--- a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java
+++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java
@@ -13,8 +13,8 @@
package io.dapr.examples.unittesting;
-import io.dapr.durabletask.Task;
-import io.dapr.durabletask.TaskCanceledException;
+import io.dapr.workflows.task.Task;
+import io.dapr.workflows.task.exception.TaskCanceledException;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowContext;
import io.dapr.workflows.WorkflowStub;
diff --git a/examples/src/main/java/io/dapr/examples/workflows/childworkflow/DemoChildWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/childworkflow/DemoChildWorkflow.java
index 1d4efbdfe7..dd89a253af 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/childworkflow/DemoChildWorkflow.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/childworkflow/DemoChildWorkflow.java
@@ -13,7 +13,7 @@
package io.dapr.examples.workflows.childworkflow;
-import io.dapr.durabletask.interruption.OrchestratorBlockedException;
+import io.dapr.workflows.task.interruption.OrchestratorBlockedException;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
diff --git a/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripWorkflow.java
index f375363edd..4b4f9d7e6d 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripWorkflow.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripWorkflow.java
@@ -13,7 +13,7 @@
package io.dapr.examples.workflows.compensation;
-import io.dapr.durabletask.TaskFailedException;
+import io.dapr.workflows.task.exception.TaskFailedException;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
diff --git a/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutWorkflow.java
index 611b1cac67..f45c245951 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutWorkflow.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutWorkflow.java
@@ -13,7 +13,7 @@
package io.dapr.examples.workflows.faninout;
-import io.dapr.durabletask.Task;
+import io.dapr.workflows.task.Task;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
diff --git a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/AuditActivity.java b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/AuditActivity.java
index a1f3816d64..9d03697dba 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/AuditActivity.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/AuditActivity.java
@@ -13,9 +13,9 @@
package io.dapr.examples.workflows.historypropagation;
-import io.dapr.durabletask.ChildWorkflowResult;
-import io.dapr.durabletask.PropagatedHistory;
-import io.dapr.durabletask.WorkflowResult;
+import io.dapr.workflows.task.history.ChildWorkflowResult;
+import io.dapr.workflows.task.history.PropagatedHistory;
+import io.dapr.workflows.task.history.WorkflowResult;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
diff --git a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/DemoFraudCheckChildWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/DemoFraudCheckChildWorkflow.java
index e0be46a53c..06356406ab 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/DemoFraudCheckChildWorkflow.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/DemoFraudCheckChildWorkflow.java
@@ -13,8 +13,8 @@
package io.dapr.examples.workflows.historypropagation;
-import io.dapr.durabletask.PropagatedHistory;
-import io.dapr.durabletask.WorkflowResult;
+import io.dapr.workflows.task.history.PropagatedHistory;
+import io.dapr.workflows.task.history.WorkflowResult;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
diff --git a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App1Workflow.java b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App1Workflow.java
index a4142afc60..92a5fdeeab 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App1Workflow.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App1Workflow.java
@@ -13,7 +13,7 @@
package io.dapr.examples.workflows.historypropagation.multiapp;
-import io.dapr.durabletask.HistoryPropagationScope;
+import io.dapr.workflows.task.history.HistoryPropagationScope;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
diff --git a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App2AuditActivity.java b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App2AuditActivity.java
index 65810a26bb..ce48fe5e76 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App2AuditActivity.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/historypropagation/multiapp/App2AuditActivity.java
@@ -13,8 +13,8 @@
package io.dapr.examples.workflows.historypropagation.multiapp;
-import io.dapr.durabletask.PropagatedHistory;
-import io.dapr.durabletask.WorkflowResult;
+import io.dapr.workflows.task.history.PropagatedHistory;
+import io.dapr.workflows.task.history.WorkflowResult;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
diff --git a/pom.xml b/pom.xml
index cb5ae7de29..2c45bb79b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -752,7 +752,6 @@
examples
testcontainers-dapr
- durabletask-client
@@ -762,7 +761,7 @@
sdk-tests
spring-boot-examples
spring-boot-sdk-tests
- durabletask-client
+ sdk-workflows
diff --git a/sdk-bom/pom.xml b/sdk-bom/pom.xml
index f3aae9bcc9..6990e82aad 100644
--- a/sdk-bom/pom.xml
+++ b/sdk-bom/pom.xml
@@ -129,11 +129,6 @@
testcontainers-dapr
${dapr.sdk.version}
-
- io.dapr
- durabletask-client
- ${dapr.sdk.version}
-
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestExecutionKeysWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestExecutionKeysWorkflow.java
index 65eb1047c4..4a43223ec5 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestExecutionKeysWorkflow.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestExecutionKeysWorkflow.java
@@ -13,7 +13,7 @@
package io.dapr.it.testcontainers.workflows;
-import io.dapr.durabletask.Task;
+import io.dapr.workflows.task.Task;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml
index 274c989424..5d1842f6b9 100644
--- a/sdk-workflows/pom.xml
+++ b/sdk-workflows/pom.xml
@@ -1,7 +1,7 @@
-
+
+
4.0.0
@@ -16,12 +16,84 @@
dapr-sdk-workflows
SDK for Workflows on Dapr
+
+ false
+ ${project.build.directory}/generated-sources
+ ${project.build.directory}/proto
+
+ 60%
+
+
io.dapr
dapr-sdk
${project.parent.version}
+
+ javax.annotation
+ javax.annotation-api
+ provided
+
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-netty
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ io.micrometer
+ micrometer-observation
+
+
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-context
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
org.mockito
mockito-core
@@ -33,22 +105,18 @@
test
- org.junit.jupiter
- junit-jupiter
+ io.grpc
+ grpc-testing
test
- io.dapr
- durabletask-client
- ${project.parent.version}
+ org.testcontainers
+ testcontainers
+ test
io.opentelemetry
- opentelemetry-api
-
-
- io.grpc
- grpc-testing
+ opentelemetry-sdk
test
@@ -59,13 +127,130 @@
org.sonatype.plugins
nexus-staging-maven-plugin
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+ ${project.build.outputDirectory}
+
+
+
+ com.googlecode.maven-download-plugin
+ download-maven-plugin
+ ${download-maven-plugin.version}
+
+
+ getOrchestratorServiceProto
+ initialize
+
+ wget
+
+
+ true
+ ${durabletask.proto.baseurl}/orchestrator_service.proto
+ orchestrator_service.proto
+ ${protobuf.input.directory}
+
+
+
+ getOrchestrationProto
+ initialize
+
+ wget
+
+
+ true
+ ${durabletask.proto.baseurl}/orchestration.proto
+ orchestration.proto
+ ${protobuf.input.directory}
+
+
+
+ getHistoryEventsProto
+ initialize
+
+ wget
+
+
+ true
+ ${durabletask.proto.baseurl}/history_events.proto
+ history_events.proto
+ ${protobuf.input.directory}
+
+
+
+ getOrchestratorActionsProto
+ initialize
+
+ wget
+
+
+ true
+ ${durabletask.proto.baseurl}/orchestrator_actions.proto
+ orchestrator_actions.proto
+ ${protobuf.input.directory}
+
+
+
+ getAttestationProto
+ initialize
+
+ wget
+
+
+ true
+ ${durabletask.proto.baseurl}/attestation.proto
+ attestation.proto
+ ${protobuf.input.directory}
+
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-maven-plugin.version}
+
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
+ ${protobuf.input.directory}
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
org.apache.maven.plugins
maven-source-plugin
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
org.apache.maven.plugins
maven-javadoc-plugin
+
+ true
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
org.jacoco
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivity.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivity.java
index 5f8c45292a..73ee839745 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivity.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivity.java
@@ -29,8 +29,8 @@
* Because activities only guarantee at least once execution, it's recommended that activity logic be implemented as
* idempotent whenever possible.
*
- *
Activities are scheduled by orchestrators using one of the {@link io.dapr.workflows.WorkflowContext#callActivity}
- * method overloads.
+ *
Activities are scheduled by orchestrators using one of the
+ * {@link io.dapr.workflows.WorkflowContext#callActivity} method overloads.
*/
public interface WorkflowActivity {
/**
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java
index 91d691525c..da72212821 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java
@@ -13,7 +13,7 @@
package io.dapr.workflows;
-import io.dapr.durabletask.PropagatedHistory;
+import io.dapr.workflows.task.history.PropagatedHistory;
import org.slf4j.Logger;
import java.util.Optional;
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java
index fd7c8a6d92..a9f0fb13b1 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Dapr Authors
+ * Copyright 2026 The Dapr Authors
* 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
@@ -13,14 +13,17 @@
package io.dapr.workflows;
-import io.dapr.durabletask.CompositeTaskFailedException;
-import io.dapr.durabletask.PropagatedHistory;
-import io.dapr.durabletask.Task;
-import io.dapr.durabletask.TaskCanceledException;
-import io.dapr.durabletask.TaskFailedException;
+import io.dapr.workflows.client.WorkflowRuntimeStatus;
+import io.dapr.workflows.task.Task;
+import io.dapr.workflows.task.client.DurableTaskClient;
+import io.dapr.workflows.task.exception.CompositeTaskFailedException;
+import io.dapr.workflows.task.exception.TaskCanceledException;
+import io.dapr.workflows.task.exception.TaskFailedException;
+import io.dapr.workflows.task.history.PropagatedHistory;
import org.slf4j.Logger;
import javax.annotation.Nullable;
+
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
@@ -30,34 +33,51 @@
import java.util.UUID;
/**
- * Context object used by workflow implementations to perform actions such as scheduling activities,
- * durable timers, waiting for external events, and for getting basic information about the current
- * workflow instance.
+ * Used by orchestrators to perform actions such as scheduling tasks, durable timers, waiting for external events,
+ * and for getting basic information about the current orchestration.
*/
public interface WorkflowContext {
-
/**
- * Get a logger only when {@code isReplaying} is false.
- * Otherwise, return a NOP (no operation) logger.
+ * Gets a logger for this workflow.
+ *
+ *
The returned logger is a no-op logger while the workflow is replaying, so that log
+ * statements in workflow code are emitted once rather than on every replay.
*
- * @return Logger
+ * @return a logger that is silenced during replay
*/
Logger getLogger();
/**
- * Gets the name of the current workflow.
+ * Gets the name of the current task orchestration.
*
- * @return the name of the current workflow
+ * @return the name of the current task orchestration
*/
String getName();
/**
- * Gets the instance ID of the current workflow.
+ * Gets the deserialized input of the current task orchestration.
+ *
+ * @param targetType the {@link Class} object associated with {@code V}
+ * @param the expected type of the orchestrator input
+ * @return the deserialized input as an object of type {@code V} or {@code null} if no input was provided.
+ */
+ V getInput(Class targetType);
+
+ /**
+ * Gets the unique ID of the current orchestration instance.
*
- * @return the instance ID of the current workflow
+ * @return the unique ID of the current orchestration instance
*/
String getInstanceId();
+ /**
+ * Gets the app ID of the current orchestration instance, if available.
+ * This is used for cross-app workflow routing.
+ *
+ * @return the app ID of the current orchestration instance, or null if not available
+ */
+ String getAppId();
+
/**
* Gets the current orchestration time in UTC.
*
@@ -66,77 +86,182 @@ public interface WorkflowContext {
Instant getCurrentInstant();
/**
- * Completes the current workflow.
+ * Gets a value indicating whether the orchestrator is currently replaying a previous execution.
*
- * @param output the serializable output of the completed Workflow.
+ * Orchestrator functions are "replayed" after being unloaded from memory to reconstruct local variable state.
+ * During a replay, previously executed tasks will be completed automatically with previously seen values
+ * that are stored in the orchestration history. One the orchestrator reaches the point in the orchestrator
+ * where it's no longer replaying existing history, this method will return {@code false}.
+ *
+ * You can use this method if you have logic that needs to run only when not replaying. For example,
+ * certain types of application logging may become too noisy when duplicated as part of replay. The
+ * application code could check to see whether the function is being replayed and then issue the log statements
+ * when this value is {@code false}.
+ *
+ * @return {@code true} if the orchestrator is replaying, otherwise {@code false}
*/
- void complete(Object output);
+ boolean isReplaying();
/**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received or is canceled when {@code timeout} expires.
+ * Returns a new {@code Task} that is completed when all tasks in {@code tasks} completes.
+ * See {@link #allOf(Task[])} for more detailed information.
*
- * If the current orchestration is not yet waiting for an event named {@code name}, then the event will be saved in
- * the orchestration instance state and dispatched immediately when this method is called. This event saving occurs
- * even if the current orchestrator cancels the wait operation before the event is received.
+ * @param tasks the list of {@code Task} objects
+ * @param the return type of the {@code Task} objects
+ * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
+ * @see #allOf(Task[])
+ */
+ Task> allOf(List> tasks);
+
+ // TODO: Update the description of allOf to be more specific about the exception behavior.
+
+ // https://github.io.dapr.workflows.task-java/issues/54
+
+ /**
+ * Returns a new {@code Task} that is completed when all the given {@code Task}s complete. If any of the given
+ * {@code Task}s complete with an exception, the returned {@code Task} will also complete with
+ * an {@link CompositeTaskFailedException} containing details of the first encountered failure.
+ * The value of the returned {@code Task} is an ordered list of
+ * the return values of the given tasks. If no tasks are provided, returns a {@code Task} completed with value
+ * {@code null}.
*
- * Orchestrators can wait for the same event name multiple times, so waiting for multiple events with the same name
- * is allowed. Each external event received by an orchestrator will complete just one task returned by this method.
+ *
This method is useful for awaiting the completion of a set of independent tasks before continuing to the next
+ * step in the orchestration, as in the following example:
+ * {@code
+ * Task t1 = ctx.callActivity("MyActivity", String.class);
+ * Task t2 = ctx.callActivity("MyActivity", String.class);
+ * Task t3 = ctx.callActivity("MyActivity", String.class);
*
- * @param name the case-insensitive name of the event to wait for
- * @param timeout the amount of time to wait before canceling the returned {@code Task}
- * @param dataType the expected class type of the event data payload
- * @param the expected type of the event data payload
- * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires
- * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received
+ * List orderedResults = ctx.allOf(t1, t2, t3).await();
+ * }
+ *
+ * Exceptions in any of the given tasks results in an unchecked {@link CompositeTaskFailedException}.
+ * This exception can be inspected to obtain failure details of individual {@link Task}s.
+ * {@code
+ * try {
+ * List orderedResults = ctx.allOf(t1, t2, t3).await();
+ * } catch (CompositeTaskFailedException e) {
+ * List exceptions = e.getExceptions()
+ * }
+ * }
+ *
+ * @param tasks the {@code Task}s
+ * @param the return type of the {@code Task} objects
+ * @return the values of the completed {@code Task} objects in the same order as the source list
*/
- Task waitForExternalEvent(String name, Duration timeout, Class dataType) throws TaskCanceledException;
+ default Task> allOf(Task... tasks) {
+ return this.allOf(Arrays.asList(tasks));
+ }
/**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received or is canceled when {@code timeout} expires.
+ * Returns a new {@code Task} that is completed when any of the tasks in {@code tasks} completes.
+ * See {@link #anyOf(Task[])} for more detailed information.
*
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
+ * @param tasks the list of {@code Task} objects
+ * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
+ * @see #anyOf(Task[])
+ */
+ Task> anyOf(List> tasks);
+
+ /**
+ * Returns a new {@code Task} that is completed when any of the given {@code Task}s complete. The value of the
+ * new {@code Task} is a reference to the completed {@code Task} object. If no tasks are provided, returns a
+ * {@code Task} that never completes.
*
- * @param name the case-insensitive name of the event to wait for
- * @param timeout the amount of time to wait before canceling the returned {@code Task}
- * @param the expected type of the event data payload
- * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires
- * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received
+ * This method is useful for waiting on multiple concurrent tasks and performing a task-specific operation when the
+ * first task completes, as in the following example:
+ * {@code
+ * Task event1 = ctx.waitForExternalEvent("Event1");
+ * Task event2 = ctx.waitForExternalEvent("Event2");
+ * Task event3 = ctx.waitForExternalEvent("Event3");
+ *
+ * Task> winner = ctx.anyOf(event1, event2, event3).await();
+ * if (winner == event1) {
+ * // ...
+ * } else if (winner == event2) {
+ * // ...
+ * } else if (winner == event3) {
+ * // ...
+ * }
+ * }
+ *
+ * The {@code anyOf} method can also be used for implementing long-running timeouts, as in the following example:
+ *
+ * {@code
+ * Task activityTask = ctx.callActivity("SlowActivity");
+ * Task timeoutTask = ctx.createTimer(Duration.ofMinutes(30));
+ *
+ * Task> winner = ctx.anyOf(activityTask, timeoutTask).await();
+ * if (winner == activityTask) {
+ * // completion case
+ * } else {
+ * // timeout case
+ * }
+ * }
+ *
+ * @param tasks the list of {@code Task} objects
+ * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
*/
- Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException;
+ default Task> anyOf(Task>... tasks) {
+ return this.anyOf(Arrays.asList(tasks));
+ }
/**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received.
+ * Creates a durable timer that expires after the specified delay.
*
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
+ *
Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
+ * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
+ * it may be visible in framework logs and the stored history state.
*
- * @param name the case-insensitive name of the event to wait for
- * @param the expected type of the event data payload
- * @return a new {@link Task} that completes when the external event is received
+ * @param name of the timer
+ * @param delay the amount of time before the timer should expire
+ * @return a new {@code Task} that completes after the specified delay
*/
- Task waitForExternalEvent(String name) throws TaskCanceledException;
+ Task createTimer(String name, Duration delay);
/**
- * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
- * received.
+ * Creates a durable timer that expires after the specified delay.
*
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
+ *
Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
+ * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
+ * it may be visible in framework logs and the stored history state.
*
- * @param name the case-insensitive name of the event to wait for
- * @param dataType the expected class type of the event data payload
- * @param the expected type of the event data payload
- * @return a new {@link Task} that completes when the external event is received
+ * @param delay the amount of time before the timer should expire
+ * @return a new {@code Task} that completes after the specified delay
*/
- default Task waitForExternalEvent(String name, Class dataType) {
- try {
- return this.waitForExternalEvent(name, null, dataType);
- } catch (TaskCanceledException e) {
- // This should never happen because of the max duration
- throw new RuntimeException("An unexpected exception was throw while waiting for an external event.", e);
- }
- }
+ Task createTimer(Duration delay);
+
+ /**
+ * Creates a durable timer that expires after the specified timestamp with specific zone.
+ *
+ * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
+ * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
+ * it may be visible in framework logs and the stored history state.
+ *
+ * @param zonedDateTime timestamp with specific zone when the timer should expire
+ * @return a new {@code Task} that completes after the specified delay
+ */
+ Task createTimer(ZonedDateTime zonedDateTime);
+
+ /**
+ * Creates a durable timer that expires after the specified timestamp with specific zone.
+ *
+ * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
+ * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
+ * it may be visible in framework logs and the stored history state.
+ *
+ * @param name for the timer
+ * @param zonedDateTime timestamp with specific zone when the timer should expire
+ * @return a new {@code Task} that completes after the specified delay
+ */
+ Task createTimer(String name, ZonedDateTime zonedDateTime);
+
+ /**
+ * Transitions the orchestration into the {@link WorkflowRuntimeStatus#COMPLETED} state with the given output.
+ *
+ * @param output the serializable output of the completed orchestration
+ */
+ void complete(Object output);
/**
* Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
@@ -144,6 +269,22 @@ default Task waitForExternalEvent(String name, Class dataType) {
* value will be the activity's output. If the activity fails, the returned {@code Task} will complete exceptionally
* with a {@link TaskFailedException}.
*
+ * Activities are the basic unit of work in a durable task orchestration. Unlike orchestrators, which are not
+ * allowed to do any I/O or call non-deterministic APIs, activities have no implementation restrictions.
+ *
+ * An activity may execute in the local machine or a remote machine. The exact behavior depends on the underlying
+ * storage provider, which is responsible for distributing tasks across machines. In general, you should never make
+ * any assumptions about where an activity will run. You should also assume at-least-once execution guarantees for
+ * activities, meaning that an activity may be executed twice if, for example, there is a process failure before
+ * the activities result is saved into storage.
+ *
+ * Both the inputs and outputs of activities are serialized and stored in durable storage. It's highly recommended
+ * to not include any sensitive data in activity inputs or outputs. It's also recommended to not use large payloads
+ * for activity inputs and outputs, which can result in expensive serialization and network utilization. For data
+ * that cannot be cheaply or safely persisted to storage, it's recommended to instead pass references
+ * (for example, a URL to a storage blog) to the data and have activities fetch the data directly as part of their
+ * implementation.
+ *
* @param name the name of the activity to call
* @param input the serializable input to pass to the activity
* @param options additional options that control the execution and processing of the activity
@@ -162,13 +303,14 @@ default Task waitForExternalEvent(String name, Class dataType) {
* @see #callActivity(String, Object, WorkflowTaskOptions, Class)
*/
default Task callActivity(String name) {
- return this.callActivity(name, null, null, Void.class);
+ return this.callActivity(name, Void.class);
}
/**
* Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
* that completes when the activity completes.
- * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description.
+ * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a
+ * complete description.
*
* @param name the name of the activity to call
* @param input the serializable input to pass to the activity
@@ -195,8 +337,8 @@ default Task callActivity(String name, Class returnType) {
/**
* Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
* that completes when the activity completes.If the activity completes successfully, the returned {@code Task}'s
- * value will be the activity's output.
- * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description.
+ * value will be the activity's output. See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a
+ * complete description.
*
* @param name the name of the activity to call
* @param input the serializable input to pass to the activity
@@ -211,7 +353,8 @@ default Task callActivity(String name, Object input, Class returnType)
/**
* Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task}
* that completes when the activity completes.
- * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description.
+ * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a
+ * complete description.
*
* @param name the name of the activity to call
* @param input the serializable input to pass to the activity
@@ -223,149 +366,92 @@ default Task callActivity(String name, Object input, WorkflowTaskOptions o
}
/**
- * Gets a value indicating whether the workflow is currently replaying a previous execution.
- *
- * Workflow functions are "replayed" after being unloaded from memory to reconstruct local variable state.
- * During a replay, previously executed tasks will be completed automatically with previously seen values
- * that are stored in the workflow history. Once the workflow reaches the point where it's no longer
- * replaying existing history, this method will return {@code false}.
- *
- *
You can use this method if you have logic that needs to run only when not replaying. For example,
- * certain types of application logging may become too noisy when duplicated as part of replay. The
- * application code could check to see whether the function is being replayed and then issue the log statements
- * when this value is {@code false}.
+ * Restarts the orchestration with a new input and clears its history. See {@link #continueAsNew(Object, boolean)}
+ * for a full description.
*
- * @return {@code true} if the workflow is replaying, otherwise {@code false}
+ * @param input the serializable input data to re-initialize the instance with
*/
- boolean isReplaying();
+ default void continueAsNew(Object input) {
+ this.continueAsNew(input, true);
+ }
/**
- * Returns a new {@code Task} that is completed when all the given {@code Task}s complete. If any of the given
- * {@code Task}s complete with an exception, the returned {@code Task} will also complete with an
- * {@link CompositeTaskFailedException} containing details of the first encountered failure.
- * The value of the returned {@code Task} is an ordered list of the return values of the given tasks.
- * If no tasks are provided, returns a {@code Task} completed with value
- * {@code null}.
+ * Restarts the orchestration with a new input and clears its history.
*
- *
This method is useful for awaiting the completion of a set of independent tasks before continuing to the next
- * step in the orchestration, as in the following example:
- *
{@code
- * Task t1 = ctx.callActivity("MyActivity", String.class);
- * Task t2 = ctx.callActivity("MyActivity", String.class);
- * Task t3 = ctx.callActivity("MyActivity", String.class);
+ * This method is primarily designed for eternal orchestrations, which are orchestrations that
+ * may not ever complete. It works by restarting the orchestration, providing it with a new input,
+ * and truncating the existing orchestration history. It allows an orchestration to continue
+ * running indefinitely without having its history grow unbounded. The benefits of periodically
+ * truncating history include decreased memory usage, decreased storage volumes, and shorter orchestrator
+ * replays when rebuilding state.
*
- * List orderedResults = ctx.allOf(List.of(t1, t2, t3)).await();
- * }
+ * The results of any incomplete tasks will be discarded when an orchestrator calls {@code continueAsNew}.
+ * For example, if a timer is scheduled and then {@code continueAsNew} is called before the timer fires, the timer
+ * event will be discarded. The only exception to this is external events. By default, if an external event is
+ * received by an orchestration but not yet processed, the event is saved in the orchestration state unit it is
+ * received by a call to {@link #waitForExternalEvent}. These events will remain in memory
+ * even after an orchestrator restarts using {@code continueAsNew}. This behavior can be disabled by specifying
+ * {@code false} for the {@code preserveUnprocessedEvents} parameter value.
*
- * Exceptions in any of the given tasks results in an unchecked {@link CompositeTaskFailedException}.
- * This exception can be inspected to obtain failure details of individual {@link Task}s.
- *
{@code
- * try {
- * List orderedResults = ctx.allOf(List.of(t1, t2, t3)).await();
- * } catch (CompositeTaskFailedException e) {
- * List exceptions = e.getExceptions()
- * }
- * }
+ * Orchestrator implementations should complete immediately after calling the{@code continueAsNew} method.
*
- * @param tasks the list of {@code Task} objects
- * @param the return type of the {@code Task} objects
- * @return the values of the completed {@code Task} objects in the same order as the source list
- * @throws CompositeTaskFailedException if the specified {@code timeout} value expires before the event is received
+ * @param input the serializable input data to re-initialize the instance with
+ * @param preserveUnprocessedEvents {@code true} to push unprocessed external events into the new orchestration
+ * history, otherwise {@code false}
*/
- Task> allOf(List> tasks) throws CompositeTaskFailedException;
+ void continueAsNew(Object input, boolean preserveUnprocessedEvents);
/**
- * Returns a new {@code Task} that is completed when any of the tasks in {@code tasks} completes.
- * See {@link #anyOf(Task[])} for more detailed information.
+ * Check if the given patch name can be applied to the orchestration.
*
- * @param tasks the list of {@code Task} objects
- * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
- * @see #anyOf(Task[])
+ * @param patchName The name of the patch to check.
+ * @return True if the given patch name can be applied to the orchestration, False otherwise.
*/
- Task> anyOf(List> tasks);
+ boolean isPatched(String patchName);
+
/**
- * Returns a new {@code Task} that is completed when any of the given {@code Task}s complete. The value of the
- * new {@code Task} is a reference to the completed {@code Task} object. If no tasks are provided, returns a
- * {@code Task} that never completes.
- *
- * This method is useful for waiting on multiple concurrent tasks and performing a task-specific operation when the
- * first task completes, as in the following example:
- *
{@code
- * Task event1 = ctx.waitForExternalEvent("Event1");
- * Task event2 = ctx.waitForExternalEvent("Event2");
- * Task event3 = ctx.waitForExternalEvent("Event3");
+ * Create a new Uuid that is safe for replay within an orchestration or operation.
*
- * Task> winner = ctx.anyOf(event1, event2, event3).await();
- * if (winner == event1) {
- * // ...
- * } else if (winner == event2) {
- * // ...
- * } else if (winner == event3) {
- * // ...
- * }
- * }
- * The {@code anyOf} method can also be used for implementing long-running timeouts, as in the following example:
- * {@code
- * Task activityTask = ctx.callActivity("SlowActivity");
- * Task timeoutTask = ctx.createTimer(Duration.ofMinutes(30));
- *
- * Task> winner = ctx.anyOf(activityTask, timeoutTask).await();
- * if (winner == activityTask) {
- * // completion case
- * } else {
- * // timeout case
- * }
- * }
+ * The default implementation of this method creates a name-based Uuid
+ * using the algorithm from RFC 4122 §4.3. The name input used to generate
+ * this value is a combination of the orchestration instance ID and an
+ * internally managed sequence number.
+ *
*
- * @param tasks the list of {@code Task} objects
- * @return a new {@code Task} that is completed when any of the given {@code Task}s complete
+ * @return a deterministic Uuid
*/
- default Task> anyOf(Task>... tasks) {
- return this.anyOf(Arrays.asList(tasks));
+ default UUID newUuid() {
+ throw new RuntimeException("No implementation found.");
}
/**
- * Creates a durable timer that expires after the specified delay.
- *
- * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
- * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However,
- * it may be visible in framework logs and the stored history state.
- *
- * @param duration the amount of time before the timer should expire
- * @return a new {@code Task} that completes after the specified delay
- */
- Task createTimer(Duration duration);
-
- /**
- * Creates a durable timer that expires after the specified timestamp with specific zone.
- *
- * Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple,
- * internally-managed timers. The workflow code doesn't need to be aware of this behavior. However,
- * it may be visible in framework logs and the stored history state.
+ * Sends an external event to another orchestration instance.
*
- * @param zonedDateTime timestamp with specific zone when the timer should expire
- * @return a new {@code Task} that completes after the specified delay
+ * @param instanceID the unique ID of the receiving orchestration instance.
+ * @param eventName the name of the event to send
*/
- Task createTimer(ZonedDateTime zonedDateTime);
+ default void sendEvent(String instanceID, String eventName) {
+ this.sendEvent(instanceID, eventName, null);
+ }
/**
- * Gets the deserialized input of the current task orchestration.
+ * Sends an external event to another orchestration instance.
*
- * @param targetType the {@link Class} object associated with {@code V}
- * @param the expected type of the workflow input
- * @return the deserialized input as an object of type {@code V} or {@code null} if no input was provided.
+ * @param instanceId the unique ID of the receiving orchestration instance.
+ * @param eventName the name of the event to send
+ * @param eventData the payload of the event to send
*/
- V getInput(Class targetType);
+ void sendEvent(String instanceId, String eventName, Object eventData);
/**
- * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes
- * when the child-workflow completes.
+ * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
+ * when the sub-orchestration completes.
*
- * See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
+ *
See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
*
- * @param name the name of the workflow to invoke
- * @return a new {@link Task} that completes when the child-workflow completes or fails
+ * @param name the name of the orchestrator to invoke
+ * @return a new {@link Task} that completes when the sub-orchestration completes or fails
* @see #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)
*/
default Task callChildWorkflow(String name) {
@@ -373,79 +459,79 @@ default Task callChildWorkflow(String name) {
}
/**
- * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes
- * when the child-workflow completes.
+ * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
+ * when the sub-orchestration completes.
*
- * See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
+ *
See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
*
- * @param name the name of the workflow to invoke
- * @param input the serializable input to send to the child-workflow
- * @return a new {@link Task} that completes when the child-workflow completes or fails
+ * @param name the name of the orchestrator to invoke
+ * @param input the serializable input to send to the sub-orchestration
+ * @return a new {@link Task} that completes when the sub-orchestration completes or fails
*/
default Task callChildWorkflow(String name, Object input) {
return this.callChildWorkflow(name, input, Void.class);
}
/**
- * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes
- * when the child-workflow completes.
+ * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
+ * when the sub-orchestration completes.
*
- * See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
+ *
See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
*
- * @param name the name of the workflow to invoke
- * @param input the serializable input to send to the child-workflow
- * @param returnType the expected class type of the child-workflow output
- * @param the expected type of the child-workflow output
- * @return a new {@link Task} that completes when the child-workflow completes or fails
+ * @param name the name of the orchestrator to invoke
+ * @param input the serializable input to send to the sub-orchestration
+ * @param returnType the expected class type of the sub-orchestration output
+ * @param the expected type of the sub-orchestration output
+ * @return a new {@link Task} that completes when the sub-orchestration completes or fails
*/
default Task callChildWorkflow(String name, Object input, Class returnType) {
return this.callChildWorkflow(name, input, null, returnType);
}
/**
- * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes
- * when the child-workflow completes.
+ * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
+ * when the sub-orchestration completes.
*
- * See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
+ *
See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
*
- * @param name the name of the workflow to invoke
- * @param input the serializable input to send to the child-workflow
- * @param instanceID the unique ID of the child-workflow
- * @param returnType the expected class type of the child-workflow output
- * @param the expected type of the child-workflow output
- * @return a new {@link Task} that completes when the child-workflow completes or fails
+ * @param name the name of the orchestrator to invoke
+ * @param input the serializable input to send to the sub-orchestration
+ * @param instanceID the unique ID of the sub-orchestration
+ * @param returnType the expected class type of the sub-orchestration output
+ * @param the expected type of the sub-orchestration output
+ * @return a new {@link Task} that completes when the sub-orchestration completes or fails
*/
default Task callChildWorkflow(String name, Object input, String instanceID, Class returnType) {
return this.callChildWorkflow(name, input, instanceID, null, returnType);
}
/**
- * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes
- * when the child-workflow completes.
+ * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
+ * when the sub-orchestration completes.
*
- * See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
+ *
See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description.
*
- * @param name the name of the workflow to invoke
- * @param input the serializable input to send to the child-workflow
- * @param instanceID the unique ID of the child-workflow
+ * @param name the name of the orchestrator to invoke
+ * @param input the serializable input to send to the sub-orchestration
+ * @param instanceID the unique ID of the sub-orchestration
* @param options additional options that control the execution and processing of the activity
- * @return a new {@link Task} that completes when the child-workflow completes or fails
+ * @return a new {@link Task} that completes when the sub-orchestration completes or fails
*/
default Task callChildWorkflow(String name, Object input, String instanceID, WorkflowTaskOptions options) {
return this.callChildWorkflow(name, input, instanceID, options, Void.class);
}
/**
- * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes
- * when the child-workflow completes. If the child-workflow completes successfully, the returned
- * {@code Task}'s value will be the activity's output. If the child-workflow fails, the returned {@code Task}
+ * Asynchronously invokes another orchestrator as a sub-orchestration and returns a {@link Task} that completes
+ * when the sub-orchestration completes. If the sub-orchestration completes successfully, the returned
+ * {@code Task}'s value will be the activity's output. If the sub-orchestration fails, the returned {@code Task}
* will complete exceptionally with a {@link TaskFailedException}.
*
- * A child-workflow has its own instance ID, history, and status that is independent of the parent workflow
- * that started it. There are many advantages to breaking down large orchestrations into child-workflows:
+ *
A sub-orchestration has its own instance ID, history, and status that is independent of the parent orchestrator
+ * that started it. There are many advantages to breaking down large orchestrations into sub-orchestrations:
*
* -
- * Splitting large orchestrations into a series of smaller child-workflows can make code more maintainable.
+ * Splitting large orchestrations into a series of smaller sub-orchestrations can make code more maintainable.
*
* -
* Distributing orchestration logic across multiple compute nodes concurrently is useful if
@@ -455,92 +541,118 @@ default Task callChildWorkflow(String name, Object input, String instanceI
* Memory usage and CPU overhead can be reduced by keeping the history of parent orchestrations smaller.
*
*
- * The disadvantage is that there is overhead associated with starting a child-workflow and processing its
- * output. This is typically only an issue for very small orchestrations.
*
- * Because child-workflows are independent of their parents, terminating a parent orchestration does not affect
- * any child-workflows. child-workflows must be terminated independently using their unique instance ID,
- * which is specified using the {@code instanceID} parameter
+ *
The disadvantage is that there is overhead associated with starting a sub-orchestration and processing its
+ * output. This is typically only an issue for very small orchestrations.
+ *
+ * Because sub-orchestrations are independent of their parents, terminating a parent orchestration does not affect
+ * any sub-orchestrations. Sub-orchestrations must be terminated independently using their unique instance ID,
+ * which is specified using the {@code instanceID} parameter.
*
- * @param name the name of the workflow to invoke
- * @param input the serializable input to send to the child-workflow
- * @param instanceID the unique ID of the child-workflow
+ * @param name the name of the orchestrator to invoke
+ * @param input the serializable input to send to the sub-orchestration
+ * @param instanceID the unique ID of the sub-orchestration
* @param options additional options that control the execution and processing of the activity
- * @param returnType the expected class type of the child-workflow output
- * @param the expected type of the child-workflow output
- * @return a new {@link Task} that completes when the child-workflow completes or fails
+ * @param returnType the expected class type of the sub-orchestration output
+ * @param the expected type of the sub-orchestration output
+ * @return a new {@link Task} that completes when the sub-orchestration completes or fails
*/
- Task callChildWorkflow(String name,
- @Nullable Object input,
- @Nullable String instanceID,
- @Nullable WorkflowTaskOptions options,
- Class returnType);
+ Task callChildWorkflow(
+ String name,
+ @Nullable Object input,
+ @Nullable String instanceID,
+ @Nullable WorkflowTaskOptions options,
+ Class returnType);
/**
- * Restarts the orchestration with a new input and clears its history. See {@link #continueAsNew(Object, boolean)}
- * for a full description.
+ * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
+ * received or is canceled when {@code timeout} expires.
*
- * @param input the serializable input data to re-initialize the instance with
+ * External clients can raise events to a waiting orchestration instance using the
+ * {@link DurableTaskClient#raiseEvent} method.
+ *
+ * If the current orchestration is not yet waiting for an event named {@code name}, then the event will be saved in
+ * the orchestration instance state and dispatched immediately when this method is called. This event saving occurs
+ * even if the current orchestrator cancels the wait operation before the event is received.
+ *
+ * Orchestrators can wait for the same event name multiple times, so waiting for multiple events with the same name
+ * is allowed. Each external event received by an orchestrator will complete just one task returned by this method.
+ *
+ *
+ * @param name the case-insensitive name of the event to wait for
+ * @param timeout the amount of time to wait before canceling the returned {@code Task}
+ * @param dataType the expected class type of the event data payload
+ * @param the expected type of the event data payload
+ * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires
+ * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received
*/
- default void continueAsNew(Object input) {
- this.continueAsNew(input, true);
- }
+ Task waitForExternalEvent(String name, Duration timeout, Class dataType) throws TaskCanceledException;
/**
- * Restarts the orchestration with a new input and clears its history.
+ * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
+ * received or is canceled when {@code timeout} expires.
*
- * This method is primarily designed for eternal orchestrations, which are orchestrations that
- * may not ever complete. It works by restarting the orchestration, providing it with a new input,
- * and truncating the existing orchestration history. It allows an orchestration to continue
- * running indefinitely without having its history grow unbounded. The benefits of periodically
- * truncating history include decreased memory usage, decreased storage volumes, and shorter orchestrator
- * replays when rebuilding state.
+ *
See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
*
- * The results of any incomplete tasks will be discarded when an orchestrator calls {@code continueAsNew}.
- * For example, if a timer is scheduled and then {@code continueAsNew} is called before the timer fires, the timer
- * event will be discarded. The only exception to this is external events. By default, if an external event is
- * received by an orchestration but not yet processed, the event is saved in the orchestration state unit it is
- * received by a call to {@link #waitForExternalEvent}. These events will remain in memory
- * even after an orchestrator restarts using {@code continueAsNew}. This behavior can be disabled by specifying
- * {@code false} for the {@code preserveUnprocessedEvents} parameter value.
+ * @param name the case-insensitive name of the event to wait for
+ * @param timeout the amount of time to wait before canceling the returned {@code Task}
+ * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires
+ * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received
+ */
+ default Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException {
+ return this.waitForExternalEvent(name, timeout, Void.class);
+ }
+
+ /**
+ * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
+ * received.
*
- * Orchestrator implementations should complete immediately after calling the{@code continueAsNew} method.
+ *
See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
*
- * @param input the serializable input data to re-initialize the instance with
- * @param preserveUnprocessedEvents {@code true} to push unprocessed external events into the new orchestration
- * history, otherwise {@code false}
+ * @param name the case-insensitive name of the event to wait for
+ * @return a new {@link Task} that completes when the external event is received
*/
- void continueAsNew(Object input, boolean preserveUnprocessedEvents);
+ default Task waitForExternalEvent(String name) {
+ return this.waitForExternalEvent(name, Void.class);
+ }
/**
- * Create a new UUID that is safe for replay within a workflow.
+ * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
+ * received.
*
- *
- * The default implementation of this method creates a name-based UUID
- * using the algorithm from RFC 4122 §4.3. The name input used to generate
- * this value is a combination of the workflow instance ID and an
- * internally managed sequence number.
- *
- * @return a deterministic UUID
+ * See {@link #waitForExternalEvent(String, Duration, Class)} for a full description.
+ *
+ * @param name the case-insensitive name of the event to wait for
+ * @param dataType the expected class type of the event data payload
+ * @param the expected type of the event data payload
+ * @return a new {@link Task} that completes when the external event is received
*/
- default UUID newUuid() {
- throw new RuntimeException("No implementation found.");
+ default Task waitForExternalEvent(String name, Class dataType) {
+ try {
+ return this.waitForExternalEvent(name, null, dataType);
+ } catch (TaskCanceledException e) {
+ // This should never happen because of the max duration
+ throw new RuntimeException("An unexpected exception was throw while waiting for an external event.", e);
+ }
}
/**
- * Set a custom status to a workflow execution.
+ * Assigns a custom status value to the current orchestration.
+ *
+ * The {@code customStatus} value is serialized and stored in orchestration state and will be made available to the
+ * orchestration status query APIs, such as {@link DurableTaskClient#getInstanceMetadata}. The serialized value
+ * must not exceed 16 KB of UTF-16 encoded text.
+ *
+ * Use {@link #clearCustomStatus()} to remove the custom status value from the orchestration state.
*
- * @param status to be set to the current execution
+ * @param customStatus A serializable value to assign as the custom status value.
*/
- void setCustomStatus(Object status);
+ void setCustomStatus(Object customStatus);
/**
- * Checks if the patch has been applied.
- *
- * @param patchName the patch name to check
- * @return true if already applied
+ * Clears the orchestration's custom status.
*/
- boolean isPatched(String patchName);
+ void clearCustomStatus();
/**
* Gets the propagated history from a parent workflow, if any was propagated.
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java
index 52cabd34d6..8b0f954155 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java
@@ -13,7 +13,7 @@
package io.dapr.workflows;
-import io.dapr.durabletask.HistoryPropagationScope;
+import io.dapr.workflows.task.history.HistoryPropagationScope;
public class WorkflowTaskOptions {
@@ -78,6 +78,43 @@ public WorkflowTaskOptions(WorkflowTaskRetryHandler retryHandler, String appId)
this(null, retryHandler, appId, null);
}
+ /**
+ * Indicates whether a retry policy was configured.
+ *
+ * @return true when a retry policy is set.
+ */
+ public boolean hasRetryPolicy() {
+ return this.retryPolicy != null;
+ }
+
+ /**
+ * Indicates whether a retry handler was configured.
+ *
+ * @return true when a retry handler is set.
+ */
+ public boolean hasRetryHandler() {
+ return this.retryHandler != null;
+ }
+
+ /**
+ * Indicates whether a non-empty target app ID was configured.
+ *
+ * @return true when an app ID is set and not empty.
+ */
+ public boolean hasAppID() {
+ return this.appId != null && !this.appId.isEmpty();
+ }
+
+ /**
+ * Indicates whether a history propagation scope other than NONE was configured.
+ *
+ * @return true when history propagation is requested.
+ */
+ public boolean hasHistoryPropagationScope() {
+ return this.historyPropagationScope != null
+ && this.historyPropagationScope != HistoryPropagationScope.NONE;
+ }
+
public WorkflowTaskRetryPolicy getRetryPolicy() {
return retryPolicy;
}
@@ -124,4 +161,118 @@ public static WorkflowTaskOptions propagateLineage() {
public static WorkflowTaskOptions propagateOwnHistory() {
return withHistoryPropagation(HistoryPropagationScope.OWN_HISTORY);
}
+
+ /**
+ * Creates a builder for {@link WorkflowTaskOptions}.
+ *
+ * @return a new builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates an empty {@link WorkflowTaskOptions}.
+ *
+ * @return a new options instance with nothing configured.
+ */
+ public static WorkflowTaskOptions create() {
+ return new WorkflowTaskOptions(null, null, null, null);
+ }
+
+ /**
+ * Creates a {@link WorkflowTaskOptions} from a retry policy.
+ *
+ * @param retryPolicy the retry policy.
+ * @return a new options instance.
+ */
+ public static WorkflowTaskOptions withRetryPolicy(WorkflowTaskRetryPolicy retryPolicy) {
+ return new WorkflowTaskOptions(retryPolicy, null, null, null);
+ }
+
+ /**
+ * Creates a {@link WorkflowTaskOptions} from a retry handler.
+ *
+ * @param retryHandler the retry handler.
+ * @return a new options instance.
+ */
+ public static WorkflowTaskOptions withRetryHandler(WorkflowTaskRetryHandler retryHandler) {
+ return new WorkflowTaskOptions(null, retryHandler, null, null);
+ }
+
+ /**
+ * Creates a {@link WorkflowTaskOptions} targeting another app.
+ *
+ * @param appId the ID of the app to call into.
+ * @return a new options instance.
+ */
+ public static WorkflowTaskOptions withAppID(String appId) {
+ return new WorkflowTaskOptions(null, null, appId, null);
+ }
+
+ /**
+ * Builder for {@link WorkflowTaskOptions}.
+ */
+ public static final class Builder {
+ private WorkflowTaskRetryPolicy retryPolicy;
+ private WorkflowTaskRetryHandler retryHandler;
+ private String appId;
+ private HistoryPropagationScope historyPropagationScope;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the retry policy.
+ *
+ * @param retryPolicy the retry policy.
+ * @return this builder.
+ */
+ public Builder retryPolicy(WorkflowTaskRetryPolicy retryPolicy) {
+ this.retryPolicy = retryPolicy;
+ return this;
+ }
+
+ /**
+ * Sets the retry handler.
+ *
+ * @param retryHandler the retry handler.
+ * @return this builder.
+ */
+ public Builder retryHandler(WorkflowTaskRetryHandler retryHandler) {
+ this.retryHandler = retryHandler;
+ return this;
+ }
+
+ /**
+ * Sets the target app ID for cross-app calls.
+ *
+ * @param appId the app ID.
+ * @return this builder.
+ */
+ public Builder appID(String appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ /**
+ * Sets the history propagation scope.
+ *
+ * @param scope the scope.
+ * @return this builder.
+ */
+ public Builder historyPropagationScope(HistoryPropagationScope scope) {
+ this.historyPropagationScope = scope;
+ return this;
+ }
+
+ /**
+ * Builds the options.
+ *
+ * @return a new {@link WorkflowTaskOptions}.
+ */
+ public WorkflowTaskOptions build() {
+ return new WorkflowTaskOptions(this.retryPolicy, this.retryHandler, this.appId, this.historyPropagationScope);
+ }
+ }
}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryContext.java
index 15fa3fd505..c8a1730a5b 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryContext.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryContext.java
@@ -13,48 +13,53 @@
package io.dapr.workflows;
-import io.dapr.workflows.client.WorkflowFailureDetails;
-import io.dapr.workflows.runtime.DefaultWorkflowContext;
+import io.dapr.workflows.task.exception.WorkflowFailureDetails;
import java.time.Duration;
-public class WorkflowTaskRetryContext {
-
- private final DefaultWorkflowContext workflowContext;
+/**
+ * Context data that's provided to {@link WorkflowTaskRetryHandler} implementations.
+ */
+public final class WorkflowTaskRetryContext {
+ private final WorkflowContext orchestrationContext;
private final int lastAttemptNumber;
private final WorkflowFailureDetails lastFailure;
private final Duration totalRetryTime;
/**
- * Constructor for WorkflowTaskRetryContext.
+ * Creates a retry context.
+ *
+ * Public so the workflow executor in a sibling package can reach it; not intended for
+ * application code.
*
- * @param workflowContext The workflow context
- * @param lastAttemptNumber The number of the previous attempt
- * @param lastFailure The failure details from the most recent failure
- * @param totalRetryTime The amount of time spent retrying
+ * @param orchestrationContext the workflow context of the failing task.
+ * @param lastAttemptNumber the number of the attempt that just failed.
+ * @param lastFailure details of the last failure.
+ * @param totalRetryTime how long retries have been running for.
*/
public WorkflowTaskRetryContext(
- DefaultWorkflowContext workflowContext,
- int lastAttemptNumber,
- WorkflowFailureDetails lastFailure,
- Duration totalRetryTime) {
- this.workflowContext = workflowContext;
+ WorkflowContext orchestrationContext,
+ int lastAttemptNumber,
+ WorkflowFailureDetails lastFailure,
+ Duration totalRetryTime) {
+ this.orchestrationContext = orchestrationContext;
this.lastAttemptNumber = lastAttemptNumber;
this.lastFailure = lastFailure;
this.totalRetryTime = totalRetryTime;
}
/**
- * Gets the context of the current workflow.
+ * Gets the context of the current orchestration.
*
- *
The workflow context can be used in retry handlers to schedule timers (via the
- * {@link DefaultWorkflowContext#createTimer} methods) for implementing delays between retries. It can also be
- * used to implement time-based retry logic by using the {@link DefaultWorkflowContext#getCurrentInstant} method.
+ *
The orchestration context can be used in retry handlers to schedule timers (via the
+ * {@link WorkflowContext#createTimer} methods) for implementing delays between retries. It can also be
+ * used to implement time-based retry logic by using the {@link WorkflowContext#getCurrentInstant} method.
+ *
*
- * @return the context of the parent workflow
+ * @return the context of the parent orchestration
*/
- public DefaultWorkflowContext getWorkflowContext() {
- return this.workflowContext;
+ public WorkflowContext getWorkflowContext() {
+ return this.orchestrationContext;
}
/**
@@ -84,5 +89,4 @@ public int getLastAttemptNumber() {
public Duration getTotalRetryTime() {
return this.totalRetryTime;
}
-
}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java
index b0e72f917b..899c718330 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java
@@ -40,11 +40,64 @@ public WorkflowTaskRetryPolicy(
Duration maxRetryInterval,
Duration retryTimeout
) {
+ // Validation lives here rather than only on the Builder because the durable task client's
+ // RetryPolicy validated in its setters, and the adapter that used to sit between the two types
+ // routed every policy through them. With the adapter gone, a policy built through these
+ // constructors would otherwise reach the executor unchecked and fail late - as an NPE part-way
+ // through a replay, or as silently wrong retry timing.
+ if (maxNumberOfAttempts == null) {
+ throw new IllegalArgumentException("maxNumberOfAttempts cannot be null.");
+ }
+ if (maxNumberOfAttempts <= 0) {
+ throw new IllegalArgumentException("The value for maxNumberOfAttempts must be greater than zero.");
+ }
+ if (firstRetryInterval == null) {
+ throw new IllegalArgumentException("firstRetryInterval cannot be null.");
+ }
+ if (firstRetryInterval.isZero() || firstRetryInterval.isNegative()) {
+ throw new IllegalArgumentException("The value for firstRetryInterval must be greater than zero.");
+ }
+ if (backoffCoefficient == null) {
+ throw new IllegalArgumentException("backoffCoefficient cannot be null.");
+ }
+ if (backoffCoefficient < 1.0) {
+ throw new IllegalArgumentException("The value for backoffCoefficient must be greater or equal to 1.0.");
+ }
+
+ // Range-check the RAW arguments, before the null coercion below. Only null means "unset"; an
+ // explicitly supplied Duration.ZERO is a real value, and v1 rejected it because
+ // ZERO < firstRetryInterval. Checking after coercion would silently accept an explicit ZERO and
+ // would also disagree with the Builder, which still rejects it.
+ if (maxRetryInterval != null && maxRetryInterval.compareTo(firstRetryInterval) < 0) {
+ throw new IllegalArgumentException("The value for maxRetryInterval must be greater than or equal to the value "
+ + "for firstRetryInterval.");
+ }
+ if (retryTimeout != null && retryTimeout.compareTo(firstRetryInterval) < 0) {
+ throw new IllegalArgumentException("The value for retryTimeout must be greater than or equal to the value "
+ + "for firstRetryInterval.");
+ }
+
this.maxNumberOfAttempts = maxNumberOfAttempts;
this.firstRetryInterval = firstRetryInterval;
this.backoffCoefficient = backoffCoefficient;
- this.maxRetryInterval = maxRetryInterval;
- this.retryTimeout = retryTimeout;
+ // The durable task client's RetryPolicy defaulted these to ZERO, and the adapter that used to
+ // sit between the two types translated "unset" into that default. With the adapter gone, the
+ // coercion has to live here: a null reaching the executor suppresses the retry timer entirely.
+ this.maxRetryInterval = maxRetryInterval == null ? Duration.ZERO : maxRetryInterval;
+ this.retryTimeout = retryTimeout == null ? Duration.ZERO : retryTimeout;
+ }
+
+ /**
+ * Creates a retry policy with the two required settings, leaving the rest at their defaults.
+ *
+ * Carried over from the durable task client's RetryPolicy, whose two-argument constructor
+ * this replaces.
+ *
+ * @param maxNumberOfAttempts Maximum number of attempts to retry the workflow.
+ * @param firstRetryInterval Interval to wait before the first retry.
+ */
+ public WorkflowTaskRetryPolicy(int maxNumberOfAttempts, Duration firstRetryInterval) {
+ this(maxNumberOfAttempts, firstRetryInterval, 1.0, null, null);
}
public int getMaxNumberOfAttempts() {
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java
index 3880c770e0..66832ad035 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java
@@ -14,16 +14,15 @@
package io.dapr.workflows.client;
import io.dapr.config.Properties;
-import io.dapr.durabletask.DurableTaskClient;
-import io.dapr.durabletask.DurableTaskGrpcClientBuilder;
-import io.dapr.durabletask.NewOrchestrationInstanceOptions;
-import io.dapr.durabletask.OrchestrationMetadata;
-import io.dapr.durabletask.PurgeResult;
import io.dapr.utils.NetworkUtils;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.internal.ApiTokenClientInterceptor;
-import io.dapr.workflows.runtime.DefaultWorkflowInstanceStatus;
import io.dapr.workflows.runtime.DefaultWorkflowState;
+import io.dapr.workflows.task.client.DurableTaskClient;
+import io.dapr.workflows.task.client.DurableTaskGrpcClientBuilder;
+import io.dapr.workflows.task.client.NewOrchestrationInstanceOptions;
+import io.dapr.workflows.task.client.OrchestrationMetadata;
+import io.dapr.workflows.task.client.PurgeResult;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannel;
import io.grpc.Status;
@@ -246,23 +245,6 @@ public void terminateWorkflow(String workflowInstanceId, @Nullable Object output
this.innerClient.terminate(workflowInstanceId, output);
}
- /**
- * Fetches workflow instance metadata from the configured durable store.
- *
- * @param instanceId the unique ID of the workflow instance to fetch
- * @param getInputsAndOutputs true to fetch the workflow instance's
- * inputs, outputs, and custom status, or false to omit them
- * @return a metadata record that describes the workflow instance and it execution status, or a default instance
- * @deprecated Use {@link #getWorkflowState(String, boolean)} instead.
- */
- @Nullable
- @Deprecated(forRemoval = true)
- public WorkflowInstanceStatus getInstanceState(String instanceId, boolean getInputsAndOutputs) {
- OrchestrationMetadata metadata = this.innerClient.getInstanceMetadata(instanceId, getInputsAndOutputs);
-
- return metadata == null ? null : new DefaultWorkflowInstanceStatus(metadata);
- }
-
/**
* Fetches workflow instance metadata from the configured durable store.
*
@@ -278,35 +260,6 @@ public WorkflowState getWorkflowState(String instanceId, boolean getInputsAndOut
return metadata == null ? null : new DefaultWorkflowState(metadata);
}
- /**
- * Waits for an workflow to start running and returns an
- * {@link WorkflowInstanceStatus} object that contains metadata about the started
- * instance and optionally its input, output, and custom status payloads.
- *
- *
A "started" workflow instance is any instance not in the Pending state.
- *
- *
If an workflow instance is already running when this method is called,
- * the method will return immediately.
- *
- * @param instanceId the unique ID of the workflow instance to wait for
- * @param timeout the amount of time to wait for the workflow instance to start
- * @param getInputsAndOutputs true to fetch the workflow instance's
- * inputs, outputs, and custom status, or false to omit them
- * @return the workflow instance metadata or null if no such instance is found
- * @throws TimeoutException when the workflow instance is not started within the specified amount of time
- * @deprecated Use {@link #waitForWorkflowStart(String, Duration, boolean)} instead.
- */
- @Deprecated(forRemoval = true)
- @Nullable
- public WorkflowInstanceStatus waitForInstanceStart(String instanceId, Duration timeout, boolean getInputsAndOutputs)
- throws TimeoutException {
-
- OrchestrationMetadata metadata = this.innerClient.waitForInstanceStart(instanceId, timeout, getInputsAndOutputs);
-
- return metadata == null ? null : new DefaultWorkflowInstanceStatus(metadata);
- }
-
-
/**
* Waits for a workflow to start running and returns an
* {@link WorkflowState} object that contains metadata about the started
@@ -333,37 +286,6 @@ public WorkflowState waitForWorkflowStart(String instanceId, Duration timeout, b
return metadata == null ? null : new DefaultWorkflowState(metadata);
}
- /**
- * Waits for an workflow to complete and returns an {@link WorkflowInstanceStatus} object that contains
- * metadata about the completed instance.
- *
- *
A "completed" workflow instance is any instance in one of the terminal states. For example, the
- * Completed, Failed, or Terminated states.
- *
- *
Workflows are long-running and could take hours, days, or months before completing.
- * Workflows can also be eternal, in which case they'll never complete unless terminated.
- * In such cases, this call may block indefinitely, so care must be taken to ensure appropriate timeouts are used.
- * If an workflow instance is already complete when this method is called, the method will return immediately.
- *
- * @param instanceId the unique ID of the workflow instance to wait for
- * @param timeout the amount of time to wait for the workflow instance to complete
- * @param getInputsAndOutputs true to fetch the workflow instance's inputs, outputs, and custom
- * status, or false to omit them
- * @return the workflow instance metadata or null if no such instance is found
- * @throws TimeoutException when the workflow instance is not completed within the specified amount of time
- * @deprecated Use {@link #waitForWorkflowCompletion(String, Duration, boolean)} instead.
- */
- @Nullable
- @Deprecated(forRemoval = true)
- public WorkflowInstanceStatus waitForInstanceCompletion(String instanceId, Duration timeout,
- boolean getInputsAndOutputs) throws TimeoutException {
-
- OrchestrationMetadata metadata = this.innerClient.waitForInstanceCompletion(instanceId, timeout,
- getInputsAndOutputs);
- return metadata == null ? null : new DefaultWorkflowInstanceStatus(metadata);
- }
-
-
/**
* Waits for an workflow to complete and returns an {@link WorkflowState} object that contains
* metadata about the completed instance.
@@ -403,24 +325,6 @@ public void raiseEvent(String workflowInstanceId, String eventName, Object event
this.innerClient.raiseEvent(workflowInstanceId, eventName, eventPayload);
}
- /**
- * Purges workflow instance state from the workflow state store.
- *
- * @param workflowInstanceId The unique ID of the workflow instance to purge.
- * @return Return true if the workflow state was found and purged successfully otherwise false.
- * @deprecated Use {@link #purgeWorkflow(String)} instead.
- */
- @Deprecated(forRemoval = true)
- public boolean purgeInstance(String workflowInstanceId) {
- PurgeResult result = this.innerClient.purgeInstance(workflowInstanceId);
-
- if (result != null) {
- return result.getDeletedInstanceCount() > 0;
- }
-
- return false;
- }
-
/**
* Purges workflow instance state from the workflow state store.
*
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowFailureDetails.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowFailureDetails.java
deleted file mode 100644
index 1adf1fe005..0000000000
--- a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowFailureDetails.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2023 The Dapr Authors
- * 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.dapr.workflows.client;
-
-/**
- * Represents a workflow failure details.
- */
-public interface WorkflowFailureDetails {
-
- /**
- * Gets the error type, which is the namespace-qualified exception type name.
- *
- * @return the error type, which is the namespace-qualified exception type name
- */
- String getErrorType();
-
- /**
- * Gets the error message.
- *
- * @return the error message
- */
- String getErrorMessage();
-
- /**
- * Gets the stack trace.
- *
- * @return the stack trace
- */
- String getStackTrace();
-
- /**
- * Checks whether the failure was caused by the provided exception class.
- *
- * @param exceptionClass the exception class to check
- * @return {@code true} if the failure was caused by the provided exception class
- */
- default boolean isCausedBy(Class extends Exception> exceptionClass) {
- throw new UnsupportedOperationException("This method is not implemented");
- }
-
-}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowInstanceStatus.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowInstanceStatus.java
deleted file mode 100644
index bdcd0087f5..0000000000
--- a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowInstanceStatus.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2023 The Dapr Authors
- * 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.dapr.workflows.client;
-
-import javax.annotation.Nullable;
-
-import java.time.Instant;
-
-/**
- * Represents a snapshot of a workflow instance's current state, including
- * metadata.
- * @deprecated Use {@link WorkflowState} instead.
- */
-@Deprecated(forRemoval = true)
-public interface WorkflowInstanceStatus {
-
- /**
- * Gets the name of the workflow.
- *
- * @return the name of the workflow
- */
- String getName();
-
- /**
- * Gets the unique ID of the workflow instance.
- *
- * @return the unique ID of the workflow instance
- */
- String getInstanceId();
-
- /**
- * Gets the current runtime status of the workflow instance at the time this
- * object was fetched.
- *
- * @return the current runtime status of the workflow instance at the time this object was fetched
- */
- WorkflowRuntimeStatus getRuntimeStatus();
-
- /**
- * Gets the workflow instance's creation time in UTC.
- *
- * @return the workflow instance's creation time in UTC
- */
- Instant getCreatedAt();
-
- /**
- * Gets the workflow instance's last updated time in UTC.
- *
- * @return the workflow instance's last updated time in UTC
- */
- Instant getLastUpdatedAt();
-
- /**
- * Gets the workflow instance's serialized input, if any, as a string value.
- *
- * @return the workflow instance's serialized input or {@code null}
- */
- String getSerializedInput();
-
- /**
- * Gets the workflow instance's serialized output, if any, as a string value.
- *
- * @return the workflow instance's serialized output or {@code null}
- */
- String getSerializedOutput();
-
- /**
- * Gets the failure details, if any, for the failed workflow instance.
- *
- *
This method returns data only if the workflow is in the
- * {@link WorkflowFailureDetails} failureDetails,
- * and only if this instance metadata was fetched with the option to include
- * output data.
- *
- * @return the failure details of the failed workflow instance or {@code null}
- */
- @Nullable
- WorkflowFailureDetails getFailureDetails();
-
- /**
- * Gets a value indicating whether the workflow instance was running at the time
- * this object was fetched.
- *
- * @return {@code true} if the workflow existed and was in a running state otherwise {@code false}
- */
- boolean isRunning();
-
- /**
- * Gets a value indicating whether the workflow instance was completed at the
- * time this object was fetched.
- *
- *
A workflow instance is considered completed when its runtime status value is
- * {@link WorkflowRuntimeStatus#COMPLETED},
- * {@link WorkflowRuntimeStatus#FAILED}, or
- * {@link WorkflowRuntimeStatus#TERMINATED}.
- *
- * @return {@code true} if the workflow was in a terminal state; otherwise {@code false}
- */
- boolean isCompleted();
-
- /**
- * Deserializes the workflow's input into an object of the specified type.
- *
- *
Deserialization is performed using the DataConverter that was
- * configured on the DurableTaskClient object that created this workflow
- * metadata object.
- *
- * @param type the class associated with the type to deserialize the input data
- * into
- * @param the type to deserialize the input data into
- * @return the deserialized input value
- * @throws IllegalStateException if the metadata was fetched without the option
- * to read inputs and outputs
- */
- T readInputAs(Class type);
-
- /**
- * Deserializes the workflow's output into an object of the specified type.
- *
- * Deserialization is performed using the DataConverter that was
- * configured on the DurableTaskClient
- * object that created this workflow metadata object.
- *
- * @param type the class associated with the type to deserialize the output data
- * into
- * @param the type to deserialize the output data into
- * @return the deserialized input value
- * @throws IllegalStateException if the metadata was fetched without the option
- * to read inputs and outputs
- */
- T readOutputAs(Class type);
-
-}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java
index 5721c8ad38..f12767cb1b 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowRuntimeStatus.java
@@ -13,6 +13,18 @@
package io.dapr.workflows.client;
+import io.dapr.durabletask.implementation.protobuf.Orchestration;
+
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_CANCELED;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_COMPLETED;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_CONTINUED_AS_NEW;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_PENDING;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_RUNNING;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_STALLED;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_SUSPENDED;
+import static io.dapr.durabletask.implementation.protobuf.Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_TERMINATED;
+
/**
* Enum describing the runtime status of a workflow.
*/
@@ -55,6 +67,74 @@ public enum WorkflowRuntimeStatus {
/**
* The workflow was suspended.
*/
- SUSPENDED
+ SUSPENDED,
+
+ /**
+ * The workflow is in a stalled state.
+ */
+ STALLED;
+ /**
+ * Maps a protobuf workflow status onto this enum.
+ *
+ * @param status the protobuf status to convert.
+ * @return the corresponding {@link WorkflowRuntimeStatus}.
+ * @throws IllegalArgumentException if the status is unknown.
+ */
+ public static WorkflowRuntimeStatus fromProtobuf(Orchestration.OrchestrationStatus status) {
+ switch (status) {
+ case ORCHESTRATION_STATUS_RUNNING:
+ return RUNNING;
+ case ORCHESTRATION_STATUS_COMPLETED:
+ return COMPLETED;
+ case ORCHESTRATION_STATUS_CONTINUED_AS_NEW:
+ return CONTINUED_AS_NEW;
+ case ORCHESTRATION_STATUS_FAILED:
+ return FAILED;
+ case ORCHESTRATION_STATUS_CANCELED:
+ return CANCELED;
+ case ORCHESTRATION_STATUS_TERMINATED:
+ return TERMINATED;
+ case ORCHESTRATION_STATUS_PENDING:
+ return PENDING;
+ case ORCHESTRATION_STATUS_SUSPENDED:
+ return SUSPENDED;
+ case ORCHESTRATION_STATUS_STALLED:
+ return STALLED;
+ default:
+ throw new IllegalArgumentException(String.format("Unknown status value: %s", status));
+ }
+ }
+
+ /**
+ * Maps this enum onto its protobuf workflow status.
+ *
+ * @param status the status to convert.
+ * @return the corresponding protobuf status.
+ * @throws IllegalArgumentException if the status is unknown.
+ */
+ public static Orchestration.OrchestrationStatus toProtobuf(WorkflowRuntimeStatus status) {
+ switch (status) {
+ case RUNNING:
+ return ORCHESTRATION_STATUS_RUNNING;
+ case COMPLETED:
+ return ORCHESTRATION_STATUS_COMPLETED;
+ case CONTINUED_AS_NEW:
+ return ORCHESTRATION_STATUS_CONTINUED_AS_NEW;
+ case FAILED:
+ return ORCHESTRATION_STATUS_FAILED;
+ case CANCELED:
+ return ORCHESTRATION_STATUS_CANCELED;
+ case TERMINATED:
+ return ORCHESTRATION_STATUS_TERMINATED;
+ case PENDING:
+ return ORCHESTRATION_STATUS_PENDING;
+ case SUSPENDED:
+ return ORCHESTRATION_STATUS_SUSPENDED;
+ case STALLED:
+ return ORCHESTRATION_STATUS_STALLED;
+ default:
+ throw new IllegalArgumentException(String.format("Unknown status value: %s", status));
+ }
+ }
}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java
index 282d1d73ee..8febd031ce 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/WorkflowState.java
@@ -13,6 +13,8 @@
package io.dapr.workflows.client;
+import io.dapr.workflows.task.exception.WorkflowFailureDetails;
+
import javax.annotation.Nullable;
import java.time.Instant;
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/internal/DefaultExecutorService.java b/sdk-workflows/src/main/java/io/dapr/workflows/internal/DefaultExecutorService.java
new file mode 100644
index 0000000000..c0e8f89bc6
--- /dev/null
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/internal/DefaultExecutorService.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2026 The Dapr Authors
+ * 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.dapr.workflows.internal;
+
+import io.dapr.config.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Builds the executor the workflow runtime falls back to when the caller supplies none.
+ *
+ * This module is compiled for Java 17, so it cannot reference
+ * {@code Executors.newVirtualThreadPerTaskExecutor()} at compile time. On Java 21 and later
+ * that factory is resolved reflectively; on Java 17 through 20 a cached thread pool is used,
+ * which is what the workflow runtime has always used.
+ *
+ *
Virtual threads are the default on Java 21+ and can be turned off with
+ * {@link Properties#WORKFLOWS_VIRTUAL_THREADS_ENABLED} — useful when activity code holds monitors
+ * across blocking calls, which pins carrier threads on Java 21 through 23.
+ *
+ *
Callers that want explicit control should pass their own executor to
+ * {@code WorkflowRuntimeBuilder.withExecutorService(...)} instead of relying on this default.
+ * An executor supplied that way is never shut down by the runtime.
+ */
+public final class DefaultExecutorService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExecutorService.class);
+
+ private static final int VIRTUAL_THREADS_SINCE = 21;
+
+ private DefaultExecutorService() {
+ }
+
+ /**
+ * Creates the default executor for the current runtime.
+ *
+ * @param properties configuration used to resolve the virtual-threads opt-out.
+ * @return a virtual-thread-per-task executor on Java 21+ unless virtual threads are disabled,
+ * otherwise a cached thread pool.
+ */
+ public static ExecutorService create(Properties properties) {
+ if (!properties.getValue(Properties.WORKFLOWS_VIRTUAL_THREADS_ENABLED)) {
+ LOGGER.info("Virtual threads are disabled by configuration, "
+ + "using a cached thread pool for workflow and activity execution");
+ return Executors.newCachedThreadPool();
+ }
+
+ if (Runtime.version().feature() >= VIRTUAL_THREADS_SINCE) {
+ try {
+ Method factory = Executors.class.getMethod("newVirtualThreadPerTaskExecutor");
+ ExecutorService executorService = (ExecutorService) factory.invoke(null);
+ LOGGER.info("Using a virtual thread per task executor for workflow and activity execution");
+ return executorService;
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Virtual threads are unavailable on this Java {} runtime, "
+ + "falling back to a cached thread pool", Runtime.version().feature(), ex);
+ }
+ }
+
+ return Executors.newCachedThreadPool();
+ }
+}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java
index 4763e90527..11c697d173 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java
@@ -13,9 +13,9 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.PropagatedHistory;
-import io.dapr.durabletask.TaskActivityContext;
import io.dapr.workflows.WorkflowActivityContext;
+import io.dapr.workflows.task.TaskActivityContext;
+import io.dapr.workflows.task.history.PropagatedHistory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java
deleted file mode 100644
index 1ee6591969..0000000000
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright 2023 The Dapr Authors
- * 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.dapr.workflows.runtime;
-
-import io.dapr.durabletask.CompositeTaskFailedException;
-import io.dapr.durabletask.PropagatedHistory;
-import io.dapr.durabletask.RetryHandler;
-import io.dapr.durabletask.RetryPolicy;
-import io.dapr.durabletask.Task;
-import io.dapr.durabletask.TaskCanceledException;
-import io.dapr.durabletask.TaskOptions;
-import io.dapr.durabletask.TaskOrchestrationContext;
-import io.dapr.workflows.WorkflowContext;
-import io.dapr.workflows.WorkflowTaskOptions;
-import io.dapr.workflows.WorkflowTaskRetryContext;
-import io.dapr.workflows.WorkflowTaskRetryHandler;
-import io.dapr.workflows.WorkflowTaskRetryPolicy;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.slf4j.helpers.NOPLogger;
-
-import javax.annotation.Nullable;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.time.ZonedDateTime;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
-
-public class DefaultWorkflowContext implements WorkflowContext {
- private final TaskOrchestrationContext innerContext;
- private final Logger logger;
-
- /**
- * Constructor for DefaultWorkflowContext.
- *
- * @param context TaskOrchestrationContext
- * @throws IllegalArgumentException if context is null
- */
- public DefaultWorkflowContext(TaskOrchestrationContext context) throws IllegalArgumentException {
- this(context, WorkflowContext.class);
- }
-
- /**
- * Constructor for DefaultWorkflowContext.
- *
- * @param context TaskOrchestrationContext
- * @param clazz Class to use for logger
- * @throws IllegalArgumentException if context is null
- */
- public DefaultWorkflowContext(TaskOrchestrationContext context, Class> clazz) throws IllegalArgumentException {
- this(context, LoggerFactory.getLogger(clazz));
- }
-
- /**
- * Constructor for DefaultWorkflowContext.
- *
- * @param context TaskOrchestrationContext
- * @param logger Logger
- * @throws IllegalArgumentException if context or logger is null
- */
- public DefaultWorkflowContext(TaskOrchestrationContext context, Logger logger)
- throws IllegalArgumentException {
- if (context == null) {
- throw new IllegalArgumentException("Context cannot be null");
- }
-
- if (logger == null) {
- throw new IllegalArgumentException("Logger cannot be null");
- }
-
- this.innerContext = context;
- this.logger = logger;
- }
-
- /**
- * {@inheritDoc}
- */
- public Logger getLogger() {
- if (this.innerContext.getIsReplaying()) {
- return NOPLogger.NOP_LOGGER;
- }
- return this.logger;
- }
-
- /**
- * {@inheritDoc}
- */
- public String getName() {
- return this.innerContext.getName();
- }
-
- /**
- * {@inheritDoc}
- */
- public String getInstanceId() {
- return this.innerContext.getInstanceId();
- }
-
- /**
- * {@inheritDoc}
- */
- public Instant getCurrentInstant() {
- return this.innerContext.getCurrentInstant();
- }
-
- /**
- * {@inheritDoc}
- */
- public void complete(Object output) {
- this.innerContext.complete(output);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Task waitForExternalEvent(String name, Duration timeout, Class dataType)
- throws TaskCanceledException {
- return this.innerContext.waitForExternalEvent(name, timeout, dataType);
- }
-
- /**
- * Waits for an event to be raised named {@code name} and returns a {@link Task}
- * that completes when the event is
- * received or is canceled when {@code timeout} expires.
- *
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full
- * description.
- *
- * @param name the case-insensitive name of the event to wait for
- * @param timeout the amount of time to wait before canceling the returned
- * {@code Task}
- * @return a new {@link Task} that completes when the external event is received
- * or when {@code timeout} expires
- * @throws TaskCanceledException if the specified {@code timeout} value expires
- * before the event is received
- */
- @Override
- public Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException {
- return this.innerContext.waitForExternalEvent(name, timeout, Void.class);
- }
-
- /**
- * Waits for an event to be raised named {@code name} and returns a {@link Task}
- * that completes when the event is
- * received.
- *
- * See {@link #waitForExternalEvent(String, Duration, Class)} for a full
- * description.
- *
- * @param name the case-insensitive name of the event to wait for
- * @return a new {@link Task} that completes when the external event is received
- */
- @Override
- public Task waitForExternalEvent(String name) throws TaskCanceledException {
- return this.innerContext.waitForExternalEvent(name, null, Void.class);
- }
-
- @Override
- public boolean isReplaying() {
- return this.innerContext.getIsReplaying();
- }
-
- /**
- * {@inheritDoc}
- */
- public Task callActivity(String name, Object input, WorkflowTaskOptions options, Class returnType) {
- TaskOptions taskOptions = toTaskOptions(options);
-
- return this.innerContext.callActivity(name, input, taskOptions, returnType);
- }
-
- /**
- * {@inheritDoc}
- */
- public Task> allOf(List> tasks) throws CompositeTaskFailedException {
- return this.innerContext.allOf(tasks);
- }
-
- /**
- * {@inheritDoc}
- */
- public Task> anyOf(List> tasks) {
- return this.innerContext.anyOf(tasks);
- }
-
- /**
- * {@inheritDoc}
- */
- public Task createTimer(Duration duration) {
- return this.innerContext.createTimer(duration);
- }
-
- @Override
- public Task createTimer(ZonedDateTime zonedDateTime) {
- return this.innerContext.createTimer(zonedDateTime);
- }
-
- /**
- * {@inheritDoc}
- */
- public T getInput(Class targetType) {
- return this.innerContext.getInput(targetType);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Task callChildWorkflow(String name, @Nullable Object input, @Nullable String instanceID,
- @Nullable WorkflowTaskOptions options, Class returnType) {
- TaskOptions taskOptions = toTaskOptions(options);
-
- return this.innerContext.callSubOrchestrator(name, input, instanceID, taskOptions, returnType);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void continueAsNew(Object input) {
- this.innerContext.continueAsNew(input);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void continueAsNew(Object input, boolean preserveUnprocessedEvents) {
- this.innerContext.continueAsNew(input, preserveUnprocessedEvents);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public UUID newUuid() {
- return this.innerContext.newUuid();
- }
-
- private TaskOptions toTaskOptions(WorkflowTaskOptions options) {
- if (options == null) {
- return null;
- }
-
- RetryPolicy retryPolicy = toRetryPolicy(options.getRetryPolicy());
- RetryHandler retryHandler = toRetryHandler(options.getRetryHandler());
-
- return TaskOptions.builder()
- .retryPolicy(retryPolicy)
- .retryHandler(retryHandler)
- .appID(options.getAppId())
- .historyPropagationScope(options.getHistoryPropagationScope())
- .build();
- }
-
- /**
- * Converts a {@link WorkflowTaskRetryPolicy} to a {@link RetryPolicy}.
- *
- * @param workflowTaskRetryPolicy The {@link WorkflowTaskRetryPolicy} being converted
- * @return A {@link RetryPolicy}
- */
- private RetryPolicy toRetryPolicy(WorkflowTaskRetryPolicy workflowTaskRetryPolicy) {
- if (workflowTaskRetryPolicy == null) {
- return null;
- }
-
- RetryPolicy retryPolicy = new RetryPolicy(
- workflowTaskRetryPolicy.getMaxNumberOfAttempts(),
- workflowTaskRetryPolicy.getFirstRetryInterval()
- );
-
- retryPolicy.setBackoffCoefficient(workflowTaskRetryPolicy.getBackoffCoefficient());
- if (workflowTaskRetryPolicy.getMaxRetryInterval() != null) {
- retryPolicy.setMaxRetryInterval(workflowTaskRetryPolicy.getMaxRetryInterval());
- }
- if (workflowTaskRetryPolicy.getRetryTimeout() != null) {
- retryPolicy.setRetryTimeout(workflowTaskRetryPolicy.getRetryTimeout());
- }
-
- return retryPolicy;
- }
-
- /**
- * Converts a {@link WorkflowTaskRetryHandler} to a {@link RetryHandler}.
- *
- * @param workflowTaskRetryHandler The {@link WorkflowTaskRetryHandler} being converted
- * @return A {@link RetryHandler}
- */
- private RetryHandler toRetryHandler(WorkflowTaskRetryHandler workflowTaskRetryHandler) {
- if (workflowTaskRetryHandler == null) {
- return null;
- }
-
- return retryContext -> {
- WorkflowTaskRetryContext workflowRetryContext = new WorkflowTaskRetryContext(
- this,
- retryContext.getLastAttemptNumber(),
- new DefaultWorkflowFailureDetails(retryContext.getLastFailure()),
- retryContext.getTotalRetryTime()
- );
-
- return workflowTaskRetryHandler.handle(workflowRetryContext);
- };
- }
-
- /**
- * Set custom status to a workflow execution.
- *
- * @param status to set to the execution
- */
- public void setCustomStatus(Object status) {
- innerContext.setCustomStatus(status);
- }
-
- public boolean isPatched(String patchName) {
- return this.innerContext.isPatched(patchName);
- }
-
- @Override
- public Optional getPropagatedHistory() {
- return this.innerContext.getPropagatedHistory();
- }
-}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowFailureDetails.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowFailureDetails.java
deleted file mode 100644
index ee6e0d8020..0000000000
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowFailureDetails.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2023 The Dapr Authors
- * 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.dapr.workflows.runtime;
-
-import io.dapr.durabletask.FailureDetails;
-import io.dapr.workflows.client.WorkflowFailureDetails;
-
-/**
- * Represents a workflow failure details.
- */
-public class DefaultWorkflowFailureDetails implements WorkflowFailureDetails {
-
- private final FailureDetails workflowFailureDetails;
-
- /**
- * Class constructor.
- *
- * @param failureDetails failure Details
- */
- public DefaultWorkflowFailureDetails(FailureDetails failureDetails) {
- this.workflowFailureDetails = failureDetails;
- }
-
- /**
- * Gets the error type, which is the namespace-qualified exception type name.
- *
- * @return the error type, which is the namespace-qualified exception type name
- */
- @Override
- public String getErrorType() {
- return workflowFailureDetails.getErrorType();
- }
-
- /**
- * Gets the error message.
- *
- * @return the error message
- */
- @Override
- public String getErrorMessage() {
- return workflowFailureDetails.getErrorMessage();
- }
-
- /**
- * Gets the stack trace.
- *
- * @return the stack trace
- */
- @Override
- public String getStackTrace() {
- return workflowFailureDetails.getStackTrace();
- }
-
- /**
- * Checks whether the failure was caused by the provided exception class.
- *
- * @param exceptionClass the exception class to check
- * @return {@code true} if the failure was caused by the provided exception class
- */
- @Override
- public boolean isCausedBy(Class extends Exception> exceptionClass) {
- return workflowFailureDetails.isCausedBy(exceptionClass);
- }
-
- @Override
- public String toString() {
- return workflowFailureDetails.toString();
- }
-
-}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowInstanceStatus.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowInstanceStatus.java
deleted file mode 100644
index 2c63dc9451..0000000000
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowInstanceStatus.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright 2023 The Dapr Authors
- * 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.dapr.workflows.runtime;
-
-import io.dapr.durabletask.FailureDetails;
-import io.dapr.durabletask.OrchestrationMetadata;
-import io.dapr.durabletask.OrchestrationRuntimeStatus;
-import io.dapr.workflows.client.WorkflowFailureDetails;
-import io.dapr.workflows.client.WorkflowInstanceStatus;
-import io.dapr.workflows.client.WorkflowRuntimeStatus;
-
-import javax.annotation.Nullable;
-
-import java.time.Instant;
-
-/**
- * Represents a snapshot of a workflow instance's current state, including
- * metadata.
- * @deprecated Use {@link DefaultWorkflowState} instead.
- */
-@Deprecated(forRemoval = true)
-public class DefaultWorkflowInstanceStatus implements WorkflowInstanceStatus {
-
- private final OrchestrationMetadata orchestrationMetadata;
-
- @Nullable
- private final WorkflowFailureDetails failureDetails;
-
- /**
- * Class constructor.
- *
- * @param orchestrationMetadata Durable task orchestration metadata
- */
- public DefaultWorkflowInstanceStatus(OrchestrationMetadata orchestrationMetadata) {
- if (orchestrationMetadata == null) {
- throw new IllegalArgumentException("OrchestrationMetadata cannot be null");
- }
- this.orchestrationMetadata = orchestrationMetadata;
-
- FailureDetails details = orchestrationMetadata.getFailureDetails();
-
- if (details != null) {
- this.failureDetails = new DefaultWorkflowFailureDetails(details);
- } else {
- this.failureDetails = null;
- }
- }
-
- /**
- * Gets the name of the workflow.
- *
- * @return the name of the workflow
- */
- public String getName() {
- return orchestrationMetadata.getName();
- }
-
- /**
- * Gets the unique ID of the workflow instance.
- *
- * @return the unique ID of the workflow instance
- */
- public String getInstanceId() {
- return orchestrationMetadata.getInstanceId();
- }
-
- /**
- * Gets the current runtime status of the workflow instance at the time this
- * object was fetched.
- *
- * @return the current runtime status of the workflow instance at the time this object was fetched
- */
- public WorkflowRuntimeStatus getRuntimeStatus() {
- OrchestrationRuntimeStatus status = orchestrationMetadata.getRuntimeStatus();
-
- return WorkflowRuntimeStatusConverter.fromOrchestrationRuntimeStatus(status);
- }
-
- /**
- * Gets the workflow instance's creation time in UTC.
- *
- * @return the workflow instance's creation time in UTC
- */
- public Instant getCreatedAt() {
- return orchestrationMetadata.getCreatedAt();
- }
-
- /**
- * Gets the workflow instance's last updated time in UTC.
- *
- * @return the workflow instance's last updated time in UTC
- */
- public Instant getLastUpdatedAt() {
- return orchestrationMetadata.getLastUpdatedAt();
- }
-
- /**
- * Gets the workflow instance's serialized input, if any, as a string value.
- *
- * @return the workflow instance's serialized input or {@code null}
- */
- public String getSerializedInput() {
- return orchestrationMetadata.getSerializedInput();
- }
-
- /**
- * Gets the workflow instance's serialized output, if any, as a string value.
- *
- * @return the workflow instance's serialized output or {@code null}
- */
- public String getSerializedOutput() {
- return orchestrationMetadata.getSerializedOutput();
- }
-
- /**
- * Gets the failure details, if any, for the failed workflow instance.
- *
- * This method returns data only if the workflow is in the
- * {@link OrchestrationRuntimeStatus#FAILED} state,
- * and only if this instance metadata was fetched with the option to include
- * output data.
- *
- * @return the failure details of the failed workflow instance or {@code null}
- */
- @Nullable
- public WorkflowFailureDetails getFailureDetails() {
- return this.failureDetails;
- }
-
- /**
- * Gets a value indicating whether the workflow instance was running at the time
- * this object was fetched.
- *
- * @return {@code true} if the workflow existed and was in a running state otherwise {@code false}
- */
- public boolean isRunning() {
- return orchestrationMetadata.isRunning();
- }
-
- /**
- * Gets a value indicating whether the workflow instance was completed at the
- * time this object was fetched.
- *
- *
A workflow instance is considered completed when its runtime status value is
- * {@link WorkflowRuntimeStatus#COMPLETED},
- * {@link WorkflowRuntimeStatus#FAILED}, or
- * {@link WorkflowRuntimeStatus#TERMINATED}.
- *
- * @return {@code true} if the workflow was in a terminal state; otherwise {@code false}
- */
- public boolean isCompleted() {
- return orchestrationMetadata.isCompleted();
- }
-
- /**
- * Deserializes the workflow's input into an object of the specified type.
- *
- *
Deserialization is performed using the DataConverter that was
- * configured on the DurableTaskClient object that created this workflow
- * metadata object.
- *
- * @param type the class associated with the type to deserialize the input data
- * into
- * @param the type to deserialize the input data into
- * @return the deserialized input value
- * @throws IllegalStateException if the metadata was fetched without the option
- * to read inputs and outputs
- */
- public T readInputAs(Class type) {
- return orchestrationMetadata.readInputAs(type);
- }
-
- /**
- * Deserializes the workflow's output into an object of the specified type.
- *
- * Deserialization is performed using the DataConverter that was
- * configured on the DurableTaskClient
- * object that created this workflow metadata object.
- *
- * @param type the class associated with the type to deserialize the output data
- * into
- * @param the type to deserialize the output data into
- * @return the deserialized input value
- * @throws IllegalStateException if the metadata was fetched without the option
- * to read inputs and outputs
- */
- public T readOutputAs(Class type) {
- return orchestrationMetadata.readOutputAs(type);
- }
-
- /**
- * Generates a user-friendly string representation of the current metadata
- * object.
- *
- * @return a user-friendly string representation of the current metadata object
- */
- public String toString() {
- return orchestrationMetadata.toString();
- }
-
-}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowState.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowState.java
index 78420d4c81..615cb64350 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowState.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowState.java
@@ -13,12 +13,10 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.FailureDetails;
-import io.dapr.durabletask.OrchestrationMetadata;
-import io.dapr.durabletask.OrchestrationRuntimeStatus;
-import io.dapr.workflows.client.WorkflowFailureDetails;
import io.dapr.workflows.client.WorkflowRuntimeStatus;
import io.dapr.workflows.client.WorkflowState;
+import io.dapr.workflows.task.client.OrchestrationMetadata;
+import io.dapr.workflows.task.exception.WorkflowFailureDetails;
import javax.annotation.Nullable;
@@ -46,13 +44,7 @@ public DefaultWorkflowState(OrchestrationMetadata orchestrationMetadata) {
}
this.orchestrationMetadata = orchestrationMetadata;
- FailureDetails details = orchestrationMetadata.getFailureDetails();
-
- if (details != null) {
- this.failureDetails = new DefaultWorkflowFailureDetails(details);
- } else {
- this.failureDetails = null;
- }
+ this.failureDetails = orchestrationMetadata.getFailureDetails();
}
/**
@@ -80,9 +72,7 @@ public String getWorkflowId() {
* @return the current runtime status of the workflow instance at the time this object was fetched
*/
public WorkflowRuntimeStatus getRuntimeStatus() {
- OrchestrationRuntimeStatus status = orchestrationMetadata.getRuntimeStatus();
-
- return WorkflowRuntimeStatusConverter.fromOrchestrationRuntimeStatus(status);
+ return orchestrationMetadata.getRuntimeStatus();
}
/**
@@ -125,7 +115,7 @@ public String getSerializedOutput() {
* Gets the failure details, if any, for the failed workflow instance.
*
* This method returns data only if the workflow is in the
- * {@link OrchestrationRuntimeStatus#FAILED} state,
+ * {@link WorkflowRuntimeStatus#FAILED} state,
* and only if this instance metadata was fetched with the option to include
* output data.
*
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapper.java
index 43bd2ca203..43601138bb 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapper.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapper.java
@@ -13,9 +13,9 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.TaskActivity;
-import io.dapr.durabletask.TaskActivityFactory;
import io.dapr.workflows.WorkflowActivity;
+import io.dapr.workflows.task.TaskActivity;
+import io.dapr.workflows.task.TaskActivityFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java
index 09d1a9d6ca..696fc14823 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java
@@ -13,9 +13,9 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.TaskActivity;
-import io.dapr.durabletask.TaskActivityFactory;
import io.dapr.workflows.WorkflowActivity;
+import io.dapr.workflows.task.TaskActivity;
+import io.dapr.workflows.task.TaskActivityFactory;
/**
* Wrapper for Durable Task Framework task activity factory.
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowClassWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowClassWrapper.java
index 8ac3789f98..6471008528 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowClassWrapper.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowClassWrapper.java
@@ -13,8 +13,8 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.TaskOrchestration;
import io.dapr.workflows.Workflow;
+import io.dapr.workflows.task.TaskOrchestration;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -70,7 +70,7 @@ public TaskOrchestration create() {
);
}
- workflow.run(new DefaultWorkflowContext(ctx, workflow.getClass()));
+ workflow.run(ctx);
};
}
}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java
index 4dbd766246..773c2c30e3 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java
@@ -13,8 +13,8 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.TaskOrchestration;
import io.dapr.workflows.Workflow;
+import io.dapr.workflows.task.TaskOrchestration;
/**
* Wrapper for Durable Task Framework orchestration factory.
@@ -41,6 +41,6 @@ public String getName() {
@Override
public TaskOrchestration create() {
- return ctx -> workflow.run(new DefaultWorkflowContext(ctx, workflow.getClass()));
+ return ctx -> workflow.run(ctx);
}
}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java
index 5f7221f6ff..28fb74dd3a 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java
@@ -13,8 +13,8 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.DurableTaskGrpcWorker;
import io.dapr.workflows.internal.GrpcChannelKeepalive;
+import io.dapr.workflows.task.worker.DurableTaskGrpcWorker;
import io.grpc.ManagedChannel;
import javax.annotation.Nullable;
@@ -31,6 +31,7 @@ public class WorkflowRuntime implements AutoCloseable {
private final ManagedChannel managedChannel;
private final ExecutorService executorService;
private final GrpcChannelKeepalive keepalive;
+ private final boolean ownsExecutorService;
/**
* Constructor.
@@ -59,10 +60,33 @@ public WorkflowRuntime(DurableTaskGrpcWorker worker,
ManagedChannel managedChannel,
ExecutorService executorService,
@Nullable GrpcChannelKeepalive keepalive) {
+ this(worker, managedChannel, executorService, keepalive, true);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param worker grpcWorker processing activities.
+ * @param managedChannel grpc channel.
+ * @param executorService executor service responsible for running the threads.
+ * @param keepalive application-level keepalive on the worker's channel, started with
+ * {@link #start()} and stopped on {@link #close()}. May be null when
+ * no keepalive is wanted.
+ * @param ownsExecutorService whether this runtime created the executor and is therefore
+ * responsible for shutting it down on {@link #close()}. Pass
+ * false for an executor owned by the caller or by a framework
+ * such as Spring, which must outlive this runtime.
+ */
+ public WorkflowRuntime(DurableTaskGrpcWorker worker,
+ ManagedChannel managedChannel,
+ ExecutorService executorService,
+ @Nullable GrpcChannelKeepalive keepalive,
+ boolean ownsExecutorService) {
this.worker = worker;
this.managedChannel = managedChannel;
this.executorService = executorService;
this.keepalive = keepalive;
+ this.ownsExecutorService = ownsExecutorService;
}
/**
@@ -114,6 +138,10 @@ private void closeSideCarChannel() {
}
private void shutDownWorkerPool() {
+ if (!this.ownsExecutorService) {
+ return;
+ }
+
this.executorService.shutdown();
try {
if (!this.executorService.awaitTermination(60, TimeUnit.SECONDS)) {
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java
index dc298e9cf4..ac29cfbcb9 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java
@@ -14,14 +14,15 @@
package io.dapr.workflows.runtime;
import io.dapr.config.Properties;
-import io.dapr.durabletask.DurableTaskGrpcWorkerBuilder;
-import io.dapr.durabletask.TaskActivityFactory;
-import io.dapr.durabletask.orchestration.TaskOrchestrationFactory;
import io.dapr.utils.NetworkUtils;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.internal.ApiTokenClientInterceptor;
+import io.dapr.workflows.internal.DefaultExecutorService;
import io.dapr.workflows.internal.GrpcChannelKeepalive;
+import io.dapr.workflows.task.TaskActivityFactory;
+import io.dapr.workflows.task.orchestration.TaskOrchestrationFactory;
+import io.dapr.workflows.task.worker.DurableTaskGrpcWorkerBuilder;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannel;
import org.apache.commons.lang3.StringUtils;
@@ -33,7 +34,6 @@
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
public class WorkflowRuntimeBuilder {
private ClientInterceptor workflowApiTokenInterceptor;
@@ -84,7 +84,10 @@ private WorkflowRuntimeBuilder(Properties properties, Logger logger) {
public WorkflowRuntime build() {
if (instance == null) {
synchronized (WorkflowRuntime.class) {
- this.executorService = this.executorService == null ? Executors.newCachedThreadPool() : this.executorService;
+ boolean ownsExecutorService = this.executorService == null;
+ if (ownsExecutorService) {
+ this.executorService = DefaultExecutorService.create(this.properties);
+ }
if (instance == null) {
GrpcChannelKeepalive keepalive = null;
if (this.properties.getValue(Properties.WORKFLOWS_RUNTIME_APP_KEEP_ALIVE_ENABLED)) {
@@ -93,7 +96,7 @@ public WorkflowRuntime build() {
}
instance = new WorkflowRuntime(
this.builder.withExecutorService(this.executorService).build(),
- this.managedChannel, this.executorService, keepalive);
+ this.managedChannel, this.executorService, keepalive, ownsExecutorService);
}
}
}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeStatusConverter.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeStatusConverter.java
deleted file mode 100644
index 2900916aaf..0000000000
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeStatusConverter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2025 The Dapr Authors
- * 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.dapr.workflows.runtime;
-
-import io.dapr.durabletask.OrchestrationRuntimeStatus;
-import io.dapr.workflows.client.WorkflowRuntimeStatus;
-
-public class WorkflowRuntimeStatusConverter {
-
- private WorkflowRuntimeStatusConverter() {
- }
-
- /**
- * Converts an OrchestrationRuntimeStatus to a WorkflowRuntimeStatus.
- *
- * @param status the OrchestrationRuntimeStatus to convert
- * @return the corresponding WorkflowRuntimeStatus
- * @throws IllegalArgumentException if the status is null or unknown
- */
- public static WorkflowRuntimeStatus fromOrchestrationRuntimeStatus(OrchestrationRuntimeStatus status) {
- if (status == null) {
- throw new IllegalArgumentException("status cannot be null");
- }
-
- switch (status) {
- case RUNNING:
- return WorkflowRuntimeStatus.RUNNING;
- case COMPLETED:
- return WorkflowRuntimeStatus.COMPLETED;
- case CONTINUED_AS_NEW:
- return WorkflowRuntimeStatus.CONTINUED_AS_NEW;
- case FAILED:
- return WorkflowRuntimeStatus.FAILED;
- case CANCELED:
- return WorkflowRuntimeStatus.CANCELED;
- case TERMINATED:
- return WorkflowRuntimeStatus.TERMINATED;
- case PENDING:
- return WorkflowRuntimeStatus.PENDING;
- case SUSPENDED:
- return WorkflowRuntimeStatus.SUSPENDED;
- default:
- throw new IllegalArgumentException(String.format("Unknown status value: %s", status));
- }
- }
-
-}
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowVersionWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowVersionWrapper.java
index 4683ebc4dd..846788bfd5 100644
--- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowVersionWrapper.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowVersionWrapper.java
@@ -13,7 +13,7 @@
package io.dapr.workflows.runtime;
-import io.dapr.durabletask.orchestration.TaskOrchestrationFactory;
+import io.dapr.workflows.task.orchestration.TaskOrchestrationFactory;
public abstract class WorkflowVersionWrapper implements TaskOrchestrationFactory {
private final String versionName;
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/OrchestratorFunction.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/OrchestratorFunction.java
similarity index 93%
rename from durabletask-client/src/main/java/io/dapr/durabletask/OrchestratorFunction.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/OrchestratorFunction.java
index a4d2f2f087..ce76c7bbe8 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/OrchestratorFunction.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/OrchestratorFunction.java
@@ -11,7 +11,9 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task;
+
+import io.dapr.workflows.WorkflowContext;
/**
* Functional interface for inline orchestrator functions.
@@ -34,5 +36,5 @@ public interface OrchestratorFunction {
* execution
* @return the serializable output of the orchestrator function
*/
- R apply(TaskOrchestrationContext ctx);
+ R apply(WorkflowContext ctx);
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/Task.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/Task.java
similarity index 90%
rename from durabletask-client/src/main/java/io/dapr/durabletask/Task.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/Task.java
index de2f13e871..2ba058d9ad 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/Task.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/Task.java
@@ -11,9 +11,11 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task;
-import io.dapr.durabletask.interruption.OrchestratorBlockedException;
+import io.dapr.workflows.WorkflowContext;
+import io.dapr.workflows.task.exception.TaskFailedException;
+import io.dapr.workflows.task.interruption.OrchestratorBlockedException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -22,7 +24,7 @@
/**
* Represents an asynchronous operation in a durable orchestration.
*
- * {@code Task} instances are created by methods on the {@link TaskOrchestrationContext} class, which is available
+ * {@code Task} instances are created by methods on the {@link WorkflowContext} class, which is available
* in {@link TaskOrchestration} implementations. For example, scheduling an activity will return a task.
*
* Task{@literal <}int{@literal >} activityTask = ctx.callActivity("MyActivity", someInput, int.class);
@@ -41,9 +43,9 @@
* @param the return type of the task
*/
public abstract class Task {
- final CompletableFuture future;
+ public final CompletableFuture future;
- Task(CompletableFuture future) {
+ public Task(CompletableFuture future) {
this.future = future;
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivity.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivity.java
similarity index 94%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskActivity.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivity.java
index 27e4291e95..7ba6aed8b9 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivity.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivity.java
@@ -11,7 +11,10 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task;
+
+import io.dapr.workflows.WorkflowContext;
+
/**
* Common interface for task activity implementations.
@@ -29,7 +32,7 @@
* Because activities only guarantee at least once execution, it's recommended that activity logic be implemented as
* idempotent whenever possible.
*
- * Activities are scheduled by orchestrators using one of the {@link TaskOrchestrationContext#callActivity} method
+ *
Activities are scheduled by orchestrators using one of the {@link WorkflowContext#callActivity} method
* overloads.
*/
@FunctionalInterface
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivityContext.java
similarity index 95%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityContext.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivityContext.java
index d8b658ed1c..b69da106da 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityContext.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivityContext.java
@@ -11,7 +11,9 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task;
+
+import io.dapr.workflows.task.history.PropagatedHistory;
import java.util.Optional;
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityFactory.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivityFactory.java
similarity index 96%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityFactory.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivityFactory.java
index e3ef45a95b..efea45fc63 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityFactory.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskActivityFactory.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task;
/**
* Factory interface for producing {@link TaskActivity} implementations.
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestration.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskOrchestration.java
similarity index 85%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestration.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/TaskOrchestration.java
index 8935313779..6c4c24728a 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestration.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/TaskOrchestration.java
@@ -11,7 +11,10 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task;
+
+import io.dapr.workflows.WorkflowContext;
+import io.dapr.workflows.task.client.DurableTaskClient;
/**
* Common interface for task orchestrator implementations.
@@ -22,7 +25,7 @@
*
* Orchestrators can be scheduled using the {@link DurableTaskClient#scheduleNewOrchestrationInstance} method
* overloads. Orchestrators can also invoke child orchestrators using the
- * {@link TaskOrchestrationContext#callSubOrchestrator} method overloads.
+ * {@link WorkflowContext#callChildWorkflow} method overloads.
*
* Orchestrators may be replayed multiple times to rebuild their local state after being reloaded into memory.
* Orchestrator code must therefore be deterministic to ensure no unexpected side effects from execution
@@ -31,8 +34,8 @@
*
* An orchestrator must not generate random numbers or random UUIDs, get the current date, read environment
* variables, or do anything else that might result in a different value if the code is replayed in the future.
- * Activities and built-in methods on the {@link TaskOrchestrationContext} parameter, like
- * {@link TaskOrchestrationContext#getCurrentInstant()}, can be used to work around these restrictions.
+ * Activities and built-in methods on the {@link WorkflowContext} parameter, like
+ * {@link WorkflowContext#getCurrentInstant()}, can be used to work around these restrictions.
*
*
* Orchestrator logic must be executed on the orchestrator thread. Creating new threads or scheduling callbacks
@@ -40,11 +43,11 @@
*
*
* Avoid infinite loops as they could cause the application to run out of memory. Instead, ensure that loops are
- * bounded or use {@link TaskOrchestrationContext#continueAsNew} to restart an orchestrator with a new input.
+ * bounded or use {@link WorkflowContext#continueAsNew} to restart an orchestrator with a new input.
*
*
* Avoid logging directly in the orchestrator code because log messages will be duplicated on each replay.
- * Instead, check the value of the {@link TaskOrchestrationContext#getIsReplaying} method and write log messages
+ * Instead, check the value of the {@link WorkflowContext#isReplaying} method and write log messages
* only when it is {@code false}.
*
*
@@ -78,5 +81,5 @@ public interface TaskOrchestration {
* @param ctx provides access to methods for scheduling durable tasks and getting information about the current
* orchestration instance.
*/
- void run(TaskOrchestrationContext ctx);
+ void run(WorkflowContext ctx);
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskClient.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskClient.java
similarity index 97%
rename from durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskClient.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskClient.java
index b08ffcc53c..4852882017 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskClient.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskClient.java
@@ -11,7 +11,9 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.client;
+
+import io.dapr.workflows.WorkflowContext;
import javax.annotation.Nullable;
@@ -96,10 +98,10 @@ public abstract String scheduleNewOrchestrationInstance(
* Sends an event notification message to a waiting orchestration instance.
*
* In order to handle the event, the target orchestration instance must be waiting for an event named
- * eventName using the {@link TaskOrchestrationContext#waitForExternalEvent(String)} method.
+ * eventName using the {@link WorkflowContext#waitForExternalEvent(String)} method.
* If the target orchestration instance is not yet waiting for an event named eventName,
* then the event will be saved in the orchestration instance state and dispatched immediately when the
- * orchestrator calls {@link TaskOrchestrationContext#waitForExternalEvent(String)}. This event saving occurs even
+ * orchestrator calls {@link WorkflowContext#waitForExternalEvent(String)}. This event saving occurs even
* if the orchestrator has canceled its wait operation before the event was received.
*
* Raised events for a completed or non-existent orchestration instance will be silently discarded.
@@ -115,10 +117,10 @@ public void raiseEvent(String instanceId, String eventName) {
* Sends an event notification message with a payload to a waiting orchestration instance.
*
* In order to handle the event, the target orchestration instance must be waiting for an event named
- * eventName using the {@link TaskOrchestrationContext#waitForExternalEvent(String)} method.
+ * eventName using the {@link WorkflowContext#waitForExternalEvent(String)} method.
* If the target orchestration instance is not yet waiting for an event named eventName,
* then the event will be saved in the orchestration instance state and dispatched immediately when the
- * orchestrator calls {@link TaskOrchestrationContext#waitForExternalEvent(String)}. This event saving occurs even
+ * orchestrator calls {@link WorkflowContext#waitForExternalEvent(String)}. This event saving occurs even
* if the orchestrator has canceled its wait operation before the event was received.
*
* Raised events for a completed or non-existent orchestration instance will be silently discarded.
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClient.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskGrpcClient.java
similarity index 98%
rename from durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClient.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskGrpcClient.java
index f258525915..34da72ac82 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClient.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskGrpcClient.java
@@ -11,13 +11,17 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.client;
import com.google.protobuf.StringValue;
import com.google.protobuf.Timestamp;
import io.dapr.durabletask.implementation.protobuf.Orchestration;
import io.dapr.durabletask.implementation.protobuf.OrchestratorService;
import io.dapr.durabletask.implementation.protobuf.TaskHubSidecarServiceGrpc;
+import io.dapr.workflows.client.WorkflowRuntimeStatus;
+import io.dapr.workflows.task.internal.Helpers;
+import io.dapr.workflows.task.serialization.DataConverter;
+import io.dapr.workflows.task.serialization.JacksonDataConverter;
import io.grpc.Channel;
import io.grpc.ChannelCredentials;
import io.grpc.Grpc;
@@ -388,7 +392,7 @@ public PurgeResult purgeInstances(PurgeInstanceCriteria purgeInstanceCriteria) t
builder.setCreatedTimeTo(DataConverter.getTimestampFromInstant(createdTimeTo)));
purgeInstanceCriteria.getRuntimeStatusList().forEach(runtimeStatus ->
Optional.ofNullable(runtimeStatus).ifPresent(status ->
- builder.addRuntimeStatus(OrchestrationRuntimeStatus.toProtobuf(status))));
+ builder.addRuntimeStatus(WorkflowRuntimeStatus.toProtobuf(status))));
Duration timeout = purgeInstanceCriteria.getTimeout();
if (timeout == null || timeout.isNegative() || timeout.isZero()) {
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClientBuilder.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskGrpcClientBuilder.java
similarity index 96%
rename from durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClientBuilder.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskGrpcClientBuilder.java
index b934fc9713..7196f9b329 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/DurableTaskGrpcClientBuilder.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/DurableTaskGrpcClientBuilder.java
@@ -1,141 +1,142 @@
-/*
- * Copyright 2025 The Dapr Authors
- * 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.dapr.durabletask;
-
-import io.grpc.Channel;
-import io.opentelemetry.api.trace.Tracer;
-
-/**
- * Builder class for constructing new {@link DurableTaskClient} objects that communicate with a sidecar process
- * over gRPC.
- */
-public final class DurableTaskGrpcClientBuilder {
- DataConverter dataConverter;
- int port;
- Channel channel;
- String tlsCaPath;
- String tlsCertPath;
- String tlsKeyPath;
- boolean insecure;
- Tracer tracer;
-
- /**
- * Sets the {@link DataConverter} to use for converting serializable data payloads.
- *
- * @param dataConverter the {@link DataConverter} to use for converting serializable data payloads
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder dataConverter(DataConverter dataConverter) {
- this.dataConverter = dataConverter;
- return this;
- }
-
- /**
- * Sets the gRPC channel to use for communicating with the sidecar process.
- *
- * This builder method allows you to provide your own gRPC channel for communicating with the Durable Task sidecar
- * endpoint. Channels provided using this method won't be closed when the client is closed.
- * Rather, the caller remains responsible for shutting down the channel after disposing the client.
- *
- * If not specified, a gRPC channel will be created automatically for each constructed
- * {@link DurableTaskClient}.
- *
- * @param channel the gRPC channel to use
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder grpcChannel(Channel channel) {
- this.channel = channel;
- return this;
- }
-
- /**
- * Sets the Tracer object to be used by DurableTaskClient to emit traces.
- *
- * @param tracer to be used by the DurableTaskClient
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder tracer(Tracer tracer) {
- this.tracer = tracer;
- return this;
- }
-
- /**
- * Sets the gRPC endpoint port to connect to. If not specified, the default Durable Task port number will be used.
- *
- * @param port the gRPC endpoint port to connect to
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder port(int port) {
- this.port = port;
- return this;
- }
-
- /**
- * Sets the path to the TLS CA certificate file for server authentication.
- * If not set, the system's default CA certificates will be used.
- *
- * @param tlsCaPath path to the TLS CA certificate file
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder tlsCaPath(String tlsCaPath) {
- this.tlsCaPath = tlsCaPath;
- return this;
- }
-
- /**
- * Sets the path to the TLS client certificate file for client authentication.
- * This is used for mTLS (mutual TLS) connections.
- *
- * @param tlsCertPath path to the TLS client certificate file
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder tlsCertPath(String tlsCertPath) {
- this.tlsCertPath = tlsCertPath;
- return this;
- }
-
- /**
- * Sets the path to the TLS client key file for client authentication.
- * This is used for mTLS (mutual TLS) connections.
- *
- * @param tlsKeyPath path to the TLS client key file
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder tlsKeyPath(String tlsKeyPath) {
- this.tlsKeyPath = tlsKeyPath;
- return this;
- }
-
- /**
- * Sets whether to use insecure (plaintext) mode for gRPC communication.
- * When set to true, TLS will be disabled and communication will be unencrypted.
- * This should only be used for development/testing.
- *
- * @param insecure whether to use insecure mode
- * @return this builder object
- */
- public DurableTaskGrpcClientBuilder insecure(boolean insecure) {
- this.insecure = insecure;
- return this;
- }
-
- /**
- * Initializes a new {@link DurableTaskClient} object with the settings specified in the current builder object.
- *
- * @return a new {@link DurableTaskClient} object
- */
- public DurableTaskClient build() {
- return new DurableTaskGrpcClient(this);
- }
-}
+/*
+ * Copyright 2025 The Dapr Authors
+ * 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.dapr.workflows.task.client;
+
+import io.dapr.workflows.task.serialization.DataConverter;
+import io.grpc.Channel;
+import io.opentelemetry.api.trace.Tracer;
+
+/**
+ * Builder class for constructing new {@link DurableTaskClient} objects that communicate with a sidecar process
+ * over gRPC.
+ */
+public final class DurableTaskGrpcClientBuilder {
+ DataConverter dataConverter;
+ int port;
+ Channel channel;
+ String tlsCaPath;
+ String tlsCertPath;
+ String tlsKeyPath;
+ boolean insecure;
+ Tracer tracer;
+
+ /**
+ * Sets the {@link DataConverter} to use for converting serializable data payloads.
+ *
+ * @param dataConverter the {@link DataConverter} to use for converting serializable data payloads
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder dataConverter(DataConverter dataConverter) {
+ this.dataConverter = dataConverter;
+ return this;
+ }
+
+ /**
+ * Sets the gRPC channel to use for communicating with the sidecar process.
+ *
+ * This builder method allows you to provide your own gRPC channel for communicating with the Durable Task sidecar
+ * endpoint. Channels provided using this method won't be closed when the client is closed.
+ * Rather, the caller remains responsible for shutting down the channel after disposing the client.
+ *
+ * If not specified, a gRPC channel will be created automatically for each constructed
+ * {@link DurableTaskClient}.
+ *
+ * @param channel the gRPC channel to use
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder grpcChannel(Channel channel) {
+ this.channel = channel;
+ return this;
+ }
+
+ /**
+ * Sets the Tracer object to be used by DurableTaskClient to emit traces.
+ *
+ * @param tracer to be used by the DurableTaskClient
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder tracer(Tracer tracer) {
+ this.tracer = tracer;
+ return this;
+ }
+
+ /**
+ * Sets the gRPC endpoint port to connect to. If not specified, the default Durable Task port number will be used.
+ *
+ * @param port the gRPC endpoint port to connect to
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder port(int port) {
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * Sets the path to the TLS CA certificate file for server authentication.
+ * If not set, the system's default CA certificates will be used.
+ *
+ * @param tlsCaPath path to the TLS CA certificate file
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder tlsCaPath(String tlsCaPath) {
+ this.tlsCaPath = tlsCaPath;
+ return this;
+ }
+
+ /**
+ * Sets the path to the TLS client certificate file for client authentication.
+ * This is used for mTLS (mutual TLS) connections.
+ *
+ * @param tlsCertPath path to the TLS client certificate file
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder tlsCertPath(String tlsCertPath) {
+ this.tlsCertPath = tlsCertPath;
+ return this;
+ }
+
+ /**
+ * Sets the path to the TLS client key file for client authentication.
+ * This is used for mTLS (mutual TLS) connections.
+ *
+ * @param tlsKeyPath path to the TLS client key file
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder tlsKeyPath(String tlsKeyPath) {
+ this.tlsKeyPath = tlsKeyPath;
+ return this;
+ }
+
+ /**
+ * Sets whether to use insecure (plaintext) mode for gRPC communication.
+ * When set to true, TLS will be disabled and communication will be unencrypted.
+ * This should only be used for development/testing.
+ *
+ * @param insecure whether to use insecure mode
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder insecure(boolean insecure) {
+ this.insecure = insecure;
+ return this;
+ }
+
+ /**
+ * Initializes a new {@link DurableTaskClient} object with the settings specified in the current builder object.
+ *
+ * @return a new {@link DurableTaskClient} object
+ */
+ public DurableTaskClient build() {
+ return new DurableTaskGrpcClient(this);
+ }
+}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/NewOrchestrationInstanceOptions.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/NewOrchestrationInstanceOptions.java
similarity index 98%
rename from durabletask-client/src/main/java/io/dapr/durabletask/NewOrchestrationInstanceOptions.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/client/NewOrchestrationInstanceOptions.java
index cac3c421ca..0b8a9a98e1 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/NewOrchestrationInstanceOptions.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/NewOrchestrationInstanceOptions.java
@@ -11,7 +11,9 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.client;
+
+import io.dapr.workflows.task.serialization.DataConverter;
import java.time.Instant;
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/OrchestrationMetadata.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/OrchestrationMetadata.java
similarity index 89%
rename from durabletask-client/src/main/java/io/dapr/durabletask/OrchestrationMetadata.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/client/OrchestrationMetadata.java
index 477b319959..e9efa27579 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/OrchestrationMetadata.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/OrchestrationMetadata.java
@@ -11,14 +11,17 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.client;
import io.dapr.durabletask.implementation.protobuf.Orchestration.WorkflowState;
import io.dapr.durabletask.implementation.protobuf.OrchestratorService;
+import io.dapr.workflows.client.WorkflowRuntimeStatus;
+import io.dapr.workflows.task.exception.WorkflowFailureDetails;
+import io.dapr.workflows.task.serialization.DataConverter;
import java.time.Instant;
-import static io.dapr.durabletask.Helpers.isNullOrEmpty;
+import static io.dapr.workflows.task.internal.Helpers.isNullOrEmpty;
/**
* Represents a snapshot of an orchestration instance's current state, including metadata.
@@ -33,13 +36,13 @@ public final class OrchestrationMetadata {
private final String name;
private final String instanceId;
- private final OrchestrationRuntimeStatus runtimeStatus;
+ private final WorkflowRuntimeStatus runtimeStatus;
private final Instant createdAt;
private final Instant lastUpdatedAt;
private final String serializedInput;
private final String serializedOutput;
private final String serializedCustomStatus;
- private final FailureDetails failureDetails;
+ private final WorkflowFailureDetails failureDetails;
OrchestrationMetadata(
OrchestratorService.GetInstanceResponse fetchResponse,
@@ -57,13 +60,13 @@ public final class OrchestrationMetadata {
this.name = state.getName();
this.instanceId = state.getInstanceId();
- this.runtimeStatus = OrchestrationRuntimeStatus.fromProtobuf(state.getWorkflowStatus());
+ this.runtimeStatus = WorkflowRuntimeStatus.fromProtobuf(state.getWorkflowStatus());
this.createdAt = DataConverter.getInstantFromTimestamp(state.getCreatedTimestamp());
this.lastUpdatedAt = DataConverter.getInstantFromTimestamp(state.getLastUpdatedTimestamp());
this.serializedInput = state.getInput().getValue();
this.serializedOutput = state.getOutput().getValue();
this.serializedCustomStatus = state.getCustomStatus().getValue();
- this.failureDetails = new FailureDetails(state.getFailureDetails());
+ this.failureDetails = new WorkflowFailureDetails(state.getFailureDetails());
}
/**
@@ -89,7 +92,7 @@ public String getInstanceId() {
*
* @return the current runtime status of the orchestration instance at the time this object was fetched
*/
- public OrchestrationRuntimeStatus getRuntimeStatus() {
+ public WorkflowRuntimeStatus getRuntimeStatus() {
return this.runtimeStatus;
}
@@ -132,12 +135,12 @@ public String getSerializedOutput() {
/**
* Gets the failure details, if any, for the failed orchestration instance.
*
- * This method returns data only if the orchestration is in the {@link OrchestrationRuntimeStatus#FAILED} state,
+ *
This method returns data only if the orchestration is in the {@link WorkflowRuntimeStatus#FAILED} state,
* and only if this instance metadata was fetched with the option to include output data.
*
* @return the failure details of the failed orchestration instance or {@code null}
*/
- public FailureDetails getFailureDetails() {
+ public WorkflowFailureDetails getFailureDetails() {
return this.failureDetails;
}
@@ -147,23 +150,23 @@ public FailureDetails getFailureDetails() {
* @return {@code true} if the orchestration existed and was in a running state; otherwise {@code false}
*/
public boolean isRunning() {
- return isInstanceFound() && this.runtimeStatus == OrchestrationRuntimeStatus.RUNNING;
+ return isInstanceFound() && this.runtimeStatus == WorkflowRuntimeStatus.RUNNING;
}
/**
* Gets a value indicating whether the orchestration instance was completed at the time this object was fetched.
*
* An orchestration instance is considered completed when its runtime status value is
- * {@link OrchestrationRuntimeStatus#COMPLETED}, {@link OrchestrationRuntimeStatus#FAILED}, or
- * {@link OrchestrationRuntimeStatus#TERMINATED}.
+ * {@link WorkflowRuntimeStatus#COMPLETED}, {@link WorkflowRuntimeStatus#FAILED}, or
+ * {@link WorkflowRuntimeStatus#TERMINATED}.
*
* @return {@code true} if the orchestration was in a terminal state; otherwise {@code false}
*/
public boolean isCompleted() {
return
- this.runtimeStatus == OrchestrationRuntimeStatus.COMPLETED
- || this.runtimeStatus == OrchestrationRuntimeStatus.FAILED
- || this.runtimeStatus == OrchestrationRuntimeStatus.TERMINATED;
+ this.runtimeStatus == WorkflowRuntimeStatus.COMPLETED
+ || this.runtimeStatus == WorkflowRuntimeStatus.FAILED
+ || this.runtimeStatus == WorkflowRuntimeStatus.TERMINATED;
}
/**
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/PurgeInstanceCriteria.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/PurgeInstanceCriteria.java
similarity index 91%
rename from durabletask-client/src/main/java/io/dapr/durabletask/PurgeInstanceCriteria.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/client/PurgeInstanceCriteria.java
index 50260c1fc7..1fde8d7aa7 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/PurgeInstanceCriteria.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/PurgeInstanceCriteria.java
@@ -11,9 +11,12 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.client;
+
+import io.dapr.workflows.client.WorkflowRuntimeStatus;
import javax.annotation.Nullable;
+
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
@@ -26,7 +29,7 @@ public final class PurgeInstanceCriteria {
private Instant createdTimeFrom;
private Instant createdTimeTo;
- private List runtimeStatusList = new ArrayList<>();
+ private List runtimeStatusList = new ArrayList<>();
private Duration timeout;
/**
@@ -66,7 +69,7 @@ public PurgeInstanceCriteria setCreatedTimeTo(Instant createdTimeTo) {
* @param runtimeStatusList the list of runtime status values to use as a selection criteria
* @return this criteria object
*/
- public PurgeInstanceCriteria setRuntimeStatusList(List runtimeStatusList) {
+ public PurgeInstanceCriteria setRuntimeStatusList(List runtimeStatusList) {
this.runtimeStatusList = runtimeStatusList;
return this;
}
@@ -108,7 +111,7 @@ public Instant getCreatedTimeTo() {
*
* @return the configured runtime status filter as a list of values
*/
- public List getRuntimeStatusList() {
+ public List getRuntimeStatusList() {
return this.runtimeStatusList;
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/PurgeResult.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/PurgeResult.java
similarity index 92%
rename from durabletask-client/src/main/java/io/dapr/durabletask/PurgeResult.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/client/PurgeResult.java
index 8d35218661..80bcba83c5 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/PurgeResult.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/client/PurgeResult.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.client;
/**
* Class representing the results of an orchestration state purge operation.
@@ -22,7 +22,7 @@ public final class PurgeResult {
private final int deletedInstanceCount;
- PurgeResult(int deletedInstanceCount) {
+ public PurgeResult(int deletedInstanceCount) {
this.deletedInstanceCount = deletedInstanceCount;
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/CompositeTaskFailedException.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/CompositeTaskFailedException.java
similarity index 77%
rename from durabletask-client/src/main/java/io/dapr/durabletask/CompositeTaskFailedException.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/exception/CompositeTaskFailedException.java
index d57ea37d2d..08c70d61d4 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/CompositeTaskFailedException.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/CompositeTaskFailedException.java
@@ -11,7 +11,9 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.exception;
+
+import io.dapr.workflows.task.Task;
import java.util.ArrayList;
import java.util.List;
@@ -34,7 +36,16 @@ public class CompositeTaskFailedException extends RuntimeException {
this.exceptions = exceptions;
}
- CompositeTaskFailedException(String message, List exceptions) {
+ /**
+ * Creates a composite failure from the tasks that failed.
+ *
+ * Public because the workflow executor constructs it from another package. Not intended for
+ * application code.
+ *
+ * @param message the exception message.
+ * @param exceptions the individual task failures.
+ */
+ public CompositeTaskFailedException(String message, List exceptions) {
super(message);
this.exceptions = exceptions;
}
@@ -49,8 +60,8 @@ public class CompositeTaskFailedException extends RuntimeException {
this.exceptions = exceptions;
}
- CompositeTaskFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace,
- List exceptions) {
+ CompositeTaskFailedException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace, List exceptions) {
super(message, cause, enableSuppression, writableStackTrace);
this.exceptions = exceptions;
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/NonDeterministicOrchestratorException.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/NonDeterministicOrchestratorException.java
similarity index 84%
rename from durabletask-client/src/main/java/io/dapr/durabletask/NonDeterministicOrchestratorException.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/exception/NonDeterministicOrchestratorException.java
index 101e6bd04a..39c12f8ed0 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/NonDeterministicOrchestratorException.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/NonDeterministicOrchestratorException.java
@@ -11,9 +11,9 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.exception;
-final class NonDeterministicOrchestratorException extends RuntimeException {
+public final class NonDeterministicOrchestratorException extends RuntimeException {
public NonDeterministicOrchestratorException(String message) {
super(message);
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskCanceledException.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/TaskCanceledException.java
similarity index 78%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskCanceledException.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/exception/TaskCanceledException.java
index 5b79882ed8..2e4e1ae778 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskCanceledException.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/TaskCanceledException.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.exception;
//@TODO: This should inherit from Exception, not TaskFailedException
@@ -20,7 +20,8 @@
*/
public final class TaskCanceledException extends TaskFailedException {
// Only intended to be created within this package
- TaskCanceledException(String message, String taskName, int taskId) {
- super(message, taskName, taskId, new FailureDetails(TaskCanceledException.class.getName(), message, "", true));
+ public TaskCanceledException(String message, String taskName, int taskId) {
+ super(message, taskName, taskId,
+ new WorkflowFailureDetails(TaskCanceledException.class.getName(), message, "", true));
}
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskFailedException.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/TaskFailedException.java
similarity index 76%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskFailedException.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/exception/TaskFailedException.java
index 5362e830c7..25a662ee6c 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskFailedException.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/TaskFailedException.java
@@ -11,7 +11,9 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.exception;
+
+import io.dapr.workflows.task.Task;
/**
* Exception that gets thrown when awaiting a {@link Task} for an activity or sub-orchestration that fails with an
@@ -21,15 +23,25 @@
* using the {@link #getErrorDetails()} method.
*/
public class TaskFailedException extends RuntimeException {
- private final FailureDetails details;
+ private final WorkflowFailureDetails details;
private final String taskName;
private final int taskId;
- TaskFailedException(String taskName, int taskId, FailureDetails details) {
+ /**
+ * Creates a task failure with a message derived from the task and failure details.
+ *
+ * Public because the workflow executor constructs it from another package. Not intended for
+ * application code.
+ *
+ * @param taskName the name of the failed task.
+ * @param taskId the id of the failed task.
+ * @param details details of the failure.
+ */
+ public TaskFailedException(String taskName, int taskId, WorkflowFailureDetails details) {
this(getExceptionMessage(taskName, taskId, details), taskName, taskId, details);
}
- TaskFailedException(String message, String taskName, int taskId, FailureDetails details) {
+ TaskFailedException(String message, String taskName, int taskId, WorkflowFailureDetails details) {
super(message);
this.taskName = taskName;
this.taskId = taskId;
@@ -64,11 +76,11 @@ public String getTaskName() {
*
* @return the details of the task failure
*/
- public FailureDetails getErrorDetails() {
+ public WorkflowFailureDetails getErrorDetails() {
return this.details;
}
- private static String getExceptionMessage(String taskName, int taskId, FailureDetails details) {
+ private static String getExceptionMessage(String taskName, int taskId, WorkflowFailureDetails details) {
return String.format("Task '%s' (#%d) failed with an unhandled exception: %s",
taskName,
taskId,
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/orchestration/exception/VersionNotRegisteredException.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/VersionNotRegisteredException.java
similarity index 92%
rename from durabletask-client/src/main/java/io/dapr/durabletask/orchestration/exception/VersionNotRegisteredException.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/exception/VersionNotRegisteredException.java
index f69ad9ea65..955e978183 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/orchestration/exception/VersionNotRegisteredException.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/VersionNotRegisteredException.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask.orchestration.exception;
+package io.dapr.workflows.task.exception;
public class VersionNotRegisteredException extends RuntimeException {
}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/FailureDetails.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/WorkflowFailureDetails.java
similarity index 54%
rename from durabletask-client/src/main/java/io/dapr/durabletask/FailureDetails.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/exception/WorkflowFailureDetails.java
index 897ac7f4e6..dc1be9634f 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/FailureDetails.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/exception/WorkflowFailureDetails.java
@@ -11,14 +11,24 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.exception;
import com.google.protobuf.StringValue;
import io.dapr.durabletask.implementation.protobuf.Orchestration.TaskFailureDetails;
+import io.dapr.workflows.task.history.PropagatedHistoryException;
+import io.dapr.workflows.task.interruption.ContinueAsNewInterruption;
+import io.dapr.workflows.task.interruption.OrchestratorBlockedException;
+import io.dapr.workflows.task.serialization.DataConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Class that represents the details of a task failure.
*
@@ -26,13 +36,64 @@
* instances of this class will expose the details of the exception. However, it's also possible that other types
* of errors could result in task failures, in which case there may not be any exception-specific information.
*/
-public final class FailureDetails {
+public final class WorkflowFailureDetails {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowFailureDetails.class);
+
+ /**
+ * Error types written into workflow history by SDK versions that shipped these exceptions under
+ * {@code io.dapr.durabletask}, mapped to where they live now.
+ *
+ * Includes the nested {@code DataConverter$DataConverterException}; enumerating the legacy
+ * types by source file name misses it, because a nested class has no file of its own.
+ *
+ *
The error type is the exception's fully qualified name and it is persisted, so a workflow
+ * started before the durable task client was folded into this module carries the old names in its
+ * history. Without this mapping {@link #isCausedBy(Class)} would silently answer {@code false} for
+ * those instances after an upgrade, sending compensation logic down the wrong branch.
+ */
+ private static final Map LEGACY_ERROR_TYPES = legacyErrorTypes();
+
+ private static Map legacyErrorTypes() {
+ Map legacy = new HashMap<>();
+ legacy.put("io.dapr.durabletask.TaskFailedException", TaskFailedException.class.getName());
+ legacy.put("io.dapr.durabletask.TaskCanceledException", TaskCanceledException.class.getName());
+ legacy.put("io.dapr.durabletask.CompositeTaskFailedException",
+ CompositeTaskFailedException.class.getName());
+ legacy.put("io.dapr.durabletask.NonDeterministicOrchestratorException",
+ NonDeterministicOrchestratorException.class.getName());
+ legacy.put("io.dapr.durabletask.PropagatedHistoryException",
+ PropagatedHistoryException.class.getName());
+ legacy.put("io.dapr.durabletask.orchestration.exception.VersionNotRegisteredException",
+ VersionNotRegisteredException.class.getName());
+ legacy.put("io.dapr.durabletask.interruption.OrchestratorBlockedException",
+ OrchestratorBlockedException.class.getName());
+ legacy.put("io.dapr.durabletask.interruption.ContinueAsNewInterruption",
+ ContinueAsNewInterruption.class.getName());
+ // Nested inside the DataConverter interface, so its binary name uses '$'. Easy to miss when
+ // enumerating exception types by file name - it has no file of its own.
+ legacy.put("io.dapr.durabletask.DataConverter$DataConverterException",
+ DataConverter.DataConverterException.class.getName());
+ return Collections.unmodifiableMap(legacy);
+ }
+
private final String errorType;
private final String errorMessage;
private final String stackTrace;
private final boolean isNonRetriable;
- FailureDetails(
+ /**
+ * Creates failure details from their individual parts.
+ *
+ * Public so the workflow executor in a sibling package can reach it; not intended for
+ * application code.
+ *
+ * @param errorType the namespace-qualified exception type name.
+ * @param errorMessage the error message, if any.
+ * @param errorDetails the stack trace, if any.
+ * @param isNonRetriable whether the failure must not be retried.
+ */
+ public WorkflowFailureDetails(
String errorType,
@Nullable String errorMessage,
@Nullable String errorDetails,
@@ -45,11 +106,19 @@ public final class FailureDetails {
this.isNonRetriable = isNonRetriable;
}
- FailureDetails(Exception exception) {
+ public WorkflowFailureDetails(Exception exception) {
this(exception.getClass().getName(), exception.getMessage(), getFullStackTrace(exception), false);
}
- FailureDetails(TaskFailureDetails proto) {
+ /**
+ * Creates failure details from their protobuf form.
+ *
+ *
Public so the workflow executor in a sibling package can reach it; not intended for
+ * application code.
+ *
+ * @param proto the protobuf failure details.
+ */
+ public WorkflowFailureDetails(TaskFailureDetails proto) {
this(proto.getErrorType(),
proto.getErrorMessage(),
proto.getStackTrace().getValue(),
@@ -113,13 +182,19 @@ public boolean isNonRetriable() {
*/
public boolean isCausedBy(Class extends Exception> exceptionClass) {
String actualClassName = this.getErrorType();
+ String resolvedClassName = LEGACY_ERROR_TYPES.getOrDefault(actualClassName, actualClassName);
+
try {
// Try using reflection to load the failure's class type and see if it's a subtype of the specified
// exception. For example, this should always succeed if exceptionClass is System.Exception.
- Class> actualExceptionClass = Class.forName(actualClassName);
+ Class> actualExceptionClass = Class.forName(resolvedClassName);
return exceptionClass.isAssignableFrom(actualExceptionClass);
} catch (ClassNotFoundException ex) {
- // Can't load the class and thus can't tell if it's related
+ // Can't load the class and thus can't tell if it's related. Say so rather than failing silently:
+ // an unloadable error type is usually history written by a different application or SDK version,
+ // and a quiet false here is indistinguishable from a genuine "not caused by".
+ LOGGER.warn("Cannot determine whether failure type '{}' is a {}: the class is not on the "
+ + "classpath, so isCausedBy is answering false.", actualClassName, exceptionClass.getName());
return false;
}
}
@@ -141,7 +216,15 @@ public static String getFullStackTrace(Throwable e) {
return sb.toString();
}
- TaskFailureDetails toProto() {
+ /**
+ * Converts these failure details to their protobuf form.
+ *
+ *
Public so the workflow executor in a sibling package can reach it; not intended for
+ * application code.
+ *
+ * @return the protobuf failure details.
+ */
+ public TaskFailureDetails toProto() {
return TaskFailureDetails.newBuilder()
.setErrorType(this.getErrorType())
.setErrorMessage(this.getErrorMessage())
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/ActivityResult.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/ActivityResult.java
similarity index 98%
rename from durabletask-client/src/main/java/io/dapr/durabletask/ActivityResult.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/history/ActivityResult.java
index a81a53782e..10fe13ec08 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/ActivityResult.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/ActivityResult.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.history;
import com.google.protobuf.StringValue;
import io.dapr.durabletask.implementation.protobuf.Orchestration.TaskFailureDetails;
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/ChildWorkflowResult.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/ChildWorkflowResult.java
similarity index 98%
rename from durabletask-client/src/main/java/io/dapr/durabletask/ChildWorkflowResult.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/history/ChildWorkflowResult.java
index 1bc015858e..add4717ce2 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/ChildWorkflowResult.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/ChildWorkflowResult.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.history;
import com.google.protobuf.StringValue;
import io.dapr.durabletask.implementation.protobuf.Orchestration.TaskFailureDetails;
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/HistoryPropagationScope.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/HistoryPropagationScope.java
similarity index 85%
rename from durabletask-client/src/main/java/io/dapr/durabletask/HistoryPropagationScope.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/history/HistoryPropagationScope.java
index 1dc6009c47..b5f6ebf16c 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/HistoryPropagationScope.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/HistoryPropagationScope.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.history;
import io.dapr.durabletask.implementation.protobuf.Orchestration;
@@ -36,7 +36,15 @@ public enum HistoryPropagationScope {
*/
LINEAGE;
- Orchestration.HistoryPropagationScope toProto() {
+ /**
+ * Converts this scope to its protobuf form.
+ *
+ *
Public so the workflow executor in a sibling package can reach it; not intended for
+ * application code.
+ *
+ * @return the protobuf scope.
+ */
+ public Orchestration.HistoryPropagationScope toProto() {
switch (this) {
case OWN_HISTORY:
return Orchestration.HistoryPropagationScope.HISTORY_PROPAGATION_SCOPE_OWN_HISTORY;
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/PropagatedHistory.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/PropagatedHistory.java
similarity index 93%
rename from durabletask-client/src/main/java/io/dapr/durabletask/PropagatedHistory.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/history/PropagatedHistory.java
index 7aef8b8bec..2d8e6c36cf 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/PropagatedHistory.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/PropagatedHistory.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.history;
import io.dapr.durabletask.implementation.protobuf.HistoryEvents;
@@ -38,7 +38,16 @@ public final class PropagatedHistory {
this.workflows = Collections.unmodifiableList(new ArrayList<>(workflows));
}
- static PropagatedHistory fromProto(HistoryEvents.PropagatedHistory proto) {
+ /**
+ * Builds a propagated history from its protobuf form.
+ *
+ *
Public so the workflow executor in a sibling package can reach it; not intended for
+ * application code.
+ *
+ * @param proto the protobuf history.
+ * @return the propagated history.
+ */
+ public static PropagatedHistory fromProto(HistoryEvents.PropagatedHistory proto) {
List workflows = proto.getChunksList().stream()
.map(WorkflowResult::fromProto)
.collect(Collectors.toList());
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/PropagatedHistoryException.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/PropagatedHistoryException.java
similarity index 96%
rename from durabletask-client/src/main/java/io/dapr/durabletask/PropagatedHistoryException.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/history/PropagatedHistoryException.java
index 89256bd9b6..f6d3ff2cc1 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/PropagatedHistoryException.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/PropagatedHistoryException.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.history;
/**
* Thrown when propagated history received from a parent workflow cannot be
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/WorkflowResult.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/WorkflowResult.java
similarity index 99%
rename from durabletask-client/src/main/java/io/dapr/durabletask/WorkflowResult.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/history/WorkflowResult.java
index edbe9e2020..6a0d655450 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/WorkflowResult.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/history/WorkflowResult.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.history;
import com.google.protobuf.InvalidProtocolBufferException;
import io.dapr.durabletask.implementation.protobuf.HistoryEvents;
diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/task/internal/Helpers.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/internal/Helpers.java
new file mode 100644
index 0000000000..1258cc0e7b
--- /dev/null
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/internal/Helpers.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * 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.dapr.workflows.task.internal;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import java.time.Duration;
+
+/**
+ * Internal argument and arithmetic helpers shared across the workflow task packages.
+ *
+ * Public only so the sibling packages can reach it. Not part of the supported API.
+ */
+public final class Helpers {
+ /** Sentinel duration meaning "no timeout". */
+ public static final Duration maxDuration = Duration.ofSeconds(Long.MAX_VALUE, 999999999L);
+
+ /**
+ * Throws when the argument is null.
+ *
+ * @param argValue the value to check.
+ * @param argName the argument name used in the error message.
+ * @param the argument type.
+ * @return the argument, when non-null.
+ */
+ public static @Nonnull V throwIfArgumentNull(@Nullable V argValue, String argName) {
+ if (argValue == null) {
+ throw new IllegalArgumentException("The argument '" + argName + "' was null.");
+ }
+
+ return argValue;
+ }
+
+ /**
+ * Throws when the argument is null, empty, or only whitespace.
+ *
+ * @param argValue the value to check.
+ * @param argName the argument name used in the error message.
+ * @return the argument, when non-blank.
+ */
+ public static @Nonnull String throwIfArgumentNullOrWhiteSpace(String argValue, String argName) {
+ throwIfArgumentNull(argValue, argName);
+ if (argValue.trim().length() == 0) {
+ throw new IllegalArgumentException("The argument '" + argName + "' was empty or contained only whitespace.");
+ }
+
+ return argValue;
+ }
+
+ /**
+ * Throws when the orchestrator has already completed.
+ *
+ * @param isComplete whether the orchestrator has completed.
+ */
+ public static void throwIfOrchestratorComplete(boolean isComplete) {
+ if (isComplete) {
+ throw new IllegalStateException("The orchestrator has already completed");
+ }
+ }
+
+ /**
+ * Indicates whether a timeout means "wait forever".
+ *
+ * @param timeout the timeout to test; may be null.
+ * @return true when the timeout is null, negative, or the maximum duration.
+ */
+ public static boolean isInfiniteTimeout(Duration timeout) {
+ return timeout == null || timeout.isNegative() || timeout.equals(maxDuration);
+ }
+
+ /**
+ * Raises base to exponent, failing loudly on overflow rather than returning infinity or zero.
+ *
+ * @param base the base.
+ * @param exponent the exponent.
+ * @return the result.
+ * @throws ArithmeticException when the result overflows.
+ */
+ public static double powExact(double base, double exponent) throws ArithmeticException {
+ if (base == 0.0) {
+ return 0.0;
+ }
+
+ double result = Math.pow(base, exponent);
+
+ if (result == Double.POSITIVE_INFINITY) {
+ throw new ArithmeticException("Double overflow resulting in POSITIVE_INFINITY");
+ } else if (result == Double.NEGATIVE_INFINITY) {
+ throw new ArithmeticException("Double overflow resulting in NEGATIVE_INFINITY");
+ } else if (Double.compare(-0.0f, result) == 0) {
+ throw new ArithmeticException("Double overflow resulting in negative zero");
+ } else if (Double.compare(+0.0f, result) == 0) {
+ throw new ArithmeticException("Double overflow resulting in positive zero");
+ }
+
+ return result;
+ }
+
+ /**
+ * Indicates whether a string is null or empty.
+ *
+ * @param s the string to test.
+ * @return true when null or empty.
+ */
+ public static boolean isNullOrEmpty(String s) {
+ return s == null || s.isEmpty();
+ }
+
+ // Cannot be instantiated
+ private Helpers() {
+ }
+}
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityExecutor.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/internal/TaskActivityExecutor.java
similarity index 94%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityExecutor.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/internal/TaskActivityExecutor.java
index 51222d98ad..bb82059371 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskActivityExecutor.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/internal/TaskActivityExecutor.java
@@ -11,11 +11,18 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.internal;
import io.dapr.durabletask.implementation.protobuf.HistoryEvents;
+import io.dapr.workflows.task.TaskActivity;
+import io.dapr.workflows.task.TaskActivityContext;
+import io.dapr.workflows.task.TaskActivityFactory;
+import io.dapr.workflows.task.history.PropagatedHistory;
+import io.dapr.workflows.task.history.PropagatedHistoryException;
+import io.dapr.workflows.task.serialization.DataConverter;
import javax.annotation.Nullable;
+
import java.util.HashMap;
import java.util.Optional;
import java.util.logging.Logger;
diff --git a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestrationExecutor.java b/sdk-workflows/src/main/java/io/dapr/workflows/task/internal/TaskOrchestrationExecutor.java
similarity index 95%
rename from durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestrationExecutor.java
rename to sdk-workflows/src/main/java/io/dapr/workflows/task/internal/TaskOrchestrationExecutor.java
index 1501856fbc..49e9897d01 100644
--- a/durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestrationExecutor.java
+++ b/sdk-workflows/src/main/java/io/dapr/workflows/task/internal/TaskOrchestrationExecutor.java
@@ -11,7 +11,7 @@
limitations under the License.
*/
-package io.dapr.durabletask;
+package io.dapr.workflows.task.internal;
import com.google.protobuf.StringValue;
import com.google.protobuf.Timestamp;
@@ -19,15 +19,30 @@
import io.dapr.durabletask.implementation.protobuf.Orchestration;
import io.dapr.durabletask.implementation.protobuf.OrchestratorActions;
import io.dapr.durabletask.implementation.protobuf.OrchestratorActions.ScheduleTaskAction.Builder;
-import io.dapr.durabletask.interruption.ContinueAsNewInterruption;
-import io.dapr.durabletask.interruption.OrchestratorBlockedException;
-import io.dapr.durabletask.orchestration.TaskOrchestrationFactories;
-import io.dapr.durabletask.orchestration.TaskOrchestrationFactory;
-import io.dapr.durabletask.orchestration.exception.VersionNotRegisteredException;
-import io.dapr.durabletask.util.UuidGenerator;
+import io.dapr.workflows.WorkflowContext;
+import io.dapr.workflows.WorkflowTaskOptions;
+import io.dapr.workflows.WorkflowTaskRetryContext;
+import io.dapr.workflows.WorkflowTaskRetryHandler;
+import io.dapr.workflows.WorkflowTaskRetryPolicy;
+import io.dapr.workflows.task.Task;
+import io.dapr.workflows.task.TaskOrchestration;
+import io.dapr.workflows.task.exception.CompositeTaskFailedException;
+import io.dapr.workflows.task.exception.NonDeterministicOrchestratorException;
+import io.dapr.workflows.task.exception.TaskCanceledException;
+import io.dapr.workflows.task.exception.TaskFailedException;
+import io.dapr.workflows.task.exception.VersionNotRegisteredException;
+import io.dapr.workflows.task.exception.WorkflowFailureDetails;
+import io.dapr.workflows.task.history.PropagatedHistory;
+import io.dapr.workflows.task.history.PropagatedHistoryException;
+import io.dapr.workflows.task.interruption.ContinueAsNewInterruption;
+import io.dapr.workflows.task.interruption.OrchestratorBlockedException;
+import io.dapr.workflows.task.orchestration.TaskOrchestrationFactories;
+import io.dapr.workflows.task.orchestration.TaskOrchestrationFactory;
+import io.dapr.workflows.task.serialization.DataConverter;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
+
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
@@ -166,12 +181,12 @@ public TaskOrchestratorResult execute(List pastEvent
context.complete(null);
} catch (PropagatedHistoryException propagatedHistoryException) {
logger.warning("The orchestrator failed parsing propagated history: " + propagatedHistoryException);
- context.fail(new FailureDetails(propagatedHistoryException));
+ context.fail(new WorkflowFailureDetails(propagatedHistoryException));
} catch (Exception e) {
// The orchestrator threw an unhandled exception - fail it
// TODO: What's the right way to log this?
logger.warning("The orchestrator failed with an unhandled exception: " + e);
- context.fail(new FailureDetails(e));
+ context.fail(new WorkflowFailureDetails(e));
}
if ((context.continuedAsNew && !context.isComplete) || (completed
@@ -188,7 +203,7 @@ public TaskOrchestratorResult execute(List pastEvent
context.encounteredPatches);
}
- private class ContextImplTask implements TaskOrchestrationContext {
+ private class ContextImplTask implements WorkflowContext {
private String orchestratorName;
private final List encounteredPatches = new ArrayList<>();
@@ -285,7 +300,7 @@ private boolean hasSourceAppId() {
return this.appId != null && !this.appId.isEmpty();
}
- private boolean hasTargetAppId(TaskOptions options) {
+ private boolean hasTargetAppId(WorkflowTaskOptions options) {
return options != null && options.hasAppID();
}
@@ -315,7 +330,16 @@ public void clearCustomStatus() {
}
@Override
- public boolean getIsReplaying() {
+ public org.slf4j.Logger getLogger() {
+ if (this.isReplaying()) {
+ return org.slf4j.helpers.NOPLogger.NOP_LOGGER;
+ }
+
+ return org.slf4j.LoggerFactory.getLogger(this.getName());
+ }
+
+ @Override
+ public boolean isReplaying() {
return this.isReplaying;
}
@@ -405,14 +429,14 @@ public Task> anyOf(List> tasks) {
public Task callActivity(
String name,
@Nullable Object input,
- @Nullable TaskOptions options,
+ @Nullable WorkflowTaskOptions options,
Class returnType) {
Helpers.throwIfOrchestratorComplete(this.isComplete);
Helpers.throwIfArgumentNull(name, "name");
Helpers.throwIfArgumentNull(returnType, "returnType");
- if (input instanceof TaskOptions) {
- throw new IllegalArgumentException("TaskOptions cannot be used as an input. "
+ if (input instanceof WorkflowTaskOptions) {
+ throw new IllegalArgumentException("WorkflowTaskOptions cannot be used as an input. "
+ "Did you call the wrong method overload?");
}
@@ -439,7 +463,7 @@ public Task callActivity(
.setId(id)
.setScheduleTask(scheduleTaskBuilder);
if (hasSourceAppId() && hasTargetAppId(options)) {
- String targetAppId = options.getAppID();
+ String targetAppId = options.getAppId();
actionBuilder.setRouter(Orchestration.TaskRouter.newBuilder()
.setSourceAppID(this.appId)
.setTargetAppID(targetAppId)
@@ -563,18 +587,18 @@ public void sendEvent(String instanceId, String eventName, Object eventData) {
}
@Override
- public Task callSubOrchestrator(
+ public Task callChildWorkflow(
String name,
@Nullable Object input,
@Nullable String instanceId,
- @Nullable TaskOptions options,
+ @Nullable WorkflowTaskOptions options,
Class returnType) {
Helpers.throwIfOrchestratorComplete(this.isComplete);
Helpers.throwIfArgumentNull(name, "name");
Helpers.throwIfArgumentNull(returnType, "returnType");
- if (input instanceof TaskOptions) {
- throw new IllegalArgumentException("TaskOptions cannot be used as an input. "
+ if (input instanceof WorkflowTaskOptions) {
+ throw new IllegalArgumentException("WorkflowTaskOptions cannot be used as an input. "
+ "Did you call the wrong method overload?");
}
@@ -609,10 +633,10 @@ public Task callSubOrchestrator(
Orchestration.TaskRouter.Builder actionRouterBuilder = Orchestration.TaskRouter.newBuilder()
.setSourceAppID(this.appId);
if (hasTargetAppId(options)) {
- actionRouterBuilder.setTargetAppID(options.getAppID());
+ actionRouterBuilder.setTargetAppID(options.getAppId());
this.logger.fine(() -> String.format(
"cross app sub-orchestration routing detected: source=%s, target=%s",
- this.appId, options.getAppID()));
+ this.appId, options.getAppId()));
}
actionBuilder.setRouter(actionRouterBuilder.build());
}
@@ -646,7 +670,7 @@ public Task callSubOrchestrator(
private Task createAppropriateTask(
TaskFactory taskFactory,
- TaskOptions options,
+ WorkflowTaskOptions options,
Consumer retryTimerOriginSetter) {
// Retry policies and retry handlers will cause us to return a RetriableTask
if (options != null && (options.hasRetryPolicy() || options.hasRetryHandler())) {
@@ -793,7 +817,7 @@ private void handleTaskFailed(HistoryEvents.HistoryEvent e) {
return;
}
- FailureDetails details = new FailureDetails(failedEvent.getFailureDetails());
+ WorkflowFailureDetails details = new WorkflowFailureDetails(failedEvent.getFailureDetails());
if (!this.isReplaying) {
// TODO: Log task failure, including the number of bytes in the result
@@ -1122,7 +1146,8 @@ private void handleSubOrchestrationFailed(HistoryEvents.HistoryEvent e) {
return;
}
- FailureDetails details = new FailureDetails(subOrchestrationInstanceFailedEvent.getFailureDetails());
+ WorkflowFailureDetails details =
+ new WorkflowFailureDetails(subOrchestrationInstanceFailedEvent.getFailureDetails());
if (!this.isReplaying) {
// TODO: Log task failure, including the number of bytes in the result
@@ -1152,7 +1177,7 @@ public void complete(Object output) {
}
}
- public void fail(FailureDetails failureDetails) {
+ public void fail(WorkflowFailureDetails failureDetails) {
// TODO: How does a parent orchestration use the output to construct an exception?
this.completeInternal(null, failureDetails,
Orchestration.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED);
@@ -1165,7 +1190,7 @@ private void completeInternal(Object output, Orchestration.OrchestrationStatus r
private void completeInternal(
@Nullable String rawOutput,
- @Nullable FailureDetails failureDetails,
+ @Nullable WorkflowFailureDetails failureDetails,
Orchestration.OrchestrationStatus runtimeStatus) {
Helpers.throwIfOrchestratorComplete(this.isComplete);
@@ -1543,24 +1568,24 @@ protected void handleException(Throwable e) {
// Task implementation that implements a retry policy
private class RetriableTask