diff --git a/.github/scripts/update_sdk_version.sh b/.github/scripts/update_sdk_version.sh
index 4bf194d13d..bc573345e3 100755
--- a/.github/scripts/update_sdk_version.sh
+++ b/.github/scripts/update_sdk_version.sh
@@ -9,25 +9,12 @@ DAPR_JAVA_SDK_VERSION=$1
DAPR_JAVA_SDK_ALPHA_VERSION=`echo $DAPR_JAVA_SDK_VERSION | sed 's/^[0-9]*\./0./'`
mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_VERSION
-mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION
+
mvn versions:set-property -Dproperty=dapr.sdk.version -DnewVersion=$DAPR_JAVA_SDK_VERSION
-mvn versions:set-property -Dproperty=dapr.sdk.version -DnewVersion=$DAPR_JAVA_SDK_VERSION -f sdk-tests/pom.xml
-mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f sdk-tests/pom.xml
+mvn versions:set-property -Dproperty=dapr.sdk.alpha.version -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION
###################
# Alpha artifacts #
###################
-# sdk-workflows
-mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f sdk-workflows/pom.xml
-
-# testcontainers-dapr
-mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f testcontainers-dapr/pom.xml
-
-# dapr-spring
-mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -DprocessDependencies=true -f dapr-spring/pom.xml
-
-# spring-boot-examples
-mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f spring-boot-examples/pom.xml
-
git clean -f
diff --git a/.github/workflows/automerge-bot.yml b/.github/workflows/automerge-bot.yml
index b2a301156a..f484c0395e 100644
--- a/.github/workflows/automerge-bot.yml
+++ b/.github/workflows/automerge-bot.yml
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Install dependencies
run: pip install PyGithub
- name: Automerge and update
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4b6805df5a..96fa4729e4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,6 +15,35 @@ on:
- release-*
jobs:
+ test:
+ name: "Unit tests"
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ continue-on-error: false
+ env:
+ JDK_VER: 17
+ steps:
+ - uses: actions/checkout@v5
+ - name: Set up OpenJDK ${{ env.JDK_VER }}
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: ${{ env.JDK_VER }}
+ - name: Run tests
+ run: ./mvnw clean install -B -q
+ - name: Codecov
+ uses: codecov/codecov-action@v5.5.0
+ - name: Upload test report for sdk
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-dapr-java-sdk-jdk${{ env.JDK_VER }}
+ path: sdk/target/jacoco-report/
+ - name: Upload test report for sdk-actors
+ uses: actions/upload-artifact@v4
+ with:
+ name: report-dapr-java-sdk-actors-jdk${{ env.JDK_VER }}
+ path: sdk-actors/target/jacoco-report/
+
build:
name: "Build jdk:${{ matrix.java }} sb:${{ matrix.spring-boot-display-version }} exp:${{ matrix.experimental }}"
runs-on: ubuntu-latest
@@ -39,7 +68,7 @@ jobs:
GOPROXY: https://proxy.golang.org
JDK_VER: ${{ matrix.java }}
DAPR_CLI_VER: 1.15.0
- DAPR_RUNTIME_VER: 1.16.0-rc.3
+ DAPR_RUNTIME_VER: 1.16.0-rc.5
DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.15.0/install/install.sh
DAPR_CLI_REF:
DAPR_REF:
@@ -50,9 +79,9 @@ jobs:
uses: docker/setup-docker-action@v4
- name: Check Docker version
run: docker version
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
@@ -64,14 +93,14 @@ jobs:
with:
go-version: ${{ env.GOVER }}
- name: Checkout Dapr CLI repo to override dapr command.
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
if: env.DAPR_CLI_REF != ''
with:
repository: dapr/cli
ref: ${{ env.DAPR_CLI_REF }}
path: cli
- name: Checkout Dapr repo to override daprd.
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
if: env.DAPR_REF != ''
with:
repository: dapr/dapr
@@ -112,33 +141,13 @@ jobs:
wget -q ${{ env.TOXIPROXY_URL }} -O /home/runner/.local/bin/toxiproxy-server
chmod +x /home/runner/.local/bin/toxiproxy-server
/home/runner/.local/bin/toxiproxy-server --version
- - name: Clean up files
- run: ./mvnw clean -B
- - name: Build sdk
- run: ./mvnw compile -B -q
- - name: Unit tests
- run: ./mvnw test # making it temporarily verbose.
- env:
- DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}
- - name: Codecov
- uses: codecov/codecov-action@v5.4.3
- - name: Install jars
- run: ./mvnw install -q -B -DskipTests
+ - name: Clean up and install sdk
+ run: ./mvnw clean install -B -q -DskipTests
- name: Integration tests using spring boot version ${{ matrix.spring-boot-version }}
id: integration_tests
- run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -Pintegration-tests verify
+ run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -Pintegration-tests dependency:copy-dependencies verify
env:
DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}
- - name: Upload test report for sdk
- uses: actions/upload-artifact@v4
- with:
- name: report-dapr-java-sdk-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
- path: sdk/target/jacoco-report/
- - name: Upload test report for sdk-actors
- uses: actions/upload-artifact@v4
- with:
- name: report-dapr-java-sdk-actors-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
- path: sdk-actors/target/jacoco-report/
- name: Upload failsafe test report for sdk-tests on failure
if: ${{ failure() && steps.integration_tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v4
@@ -154,7 +163,7 @@ jobs:
publish:
runs-on: ubuntu-latest
- needs: build
+ needs: [ build, test ]
timeout-minutes: 30
env:
JDK_VER: 17
@@ -163,9 +172,9 @@ jobs:
GPG_KEY: ${{ secrets.GPG_KEY }}
GPG_PWD: ${{ secrets.GPG_PWD }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index 5e529508bd..397aea3528 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -29,13 +29,13 @@ jobs:
JDK_VER: '17'
steps:
- name: Check out code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.DAPR_BOT_TOKEN }}
persist-credentials: false
- name: Set up OpenJDK ${{ env.JDK_VER }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml
index 25e15cbaad..19ced2735c 100644
--- a/.github/workflows/fossa.yml
+++ b/.github/workflows/fossa.yml
@@ -32,7 +32,7 @@ jobs:
FOSSA_API_KEY: b88e1f4287c3108c8751bf106fb46db6 # This is a push-only token that is safe to be exposed.
steps:
- name: "Checkout code"
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: "Run FOSSA Scan"
uses: fossas/fossa-action@v1.7.0 # Use a specific version if locking is preferred
diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml
index fa96626124..20c9f6c382 100644
--- a/.github/workflows/validate-docs.yml
+++ b/.github/workflows/validate-docs.yml
@@ -22,9 +22,9 @@ jobs:
env:
JDK_VER: 17
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index dd052bbcd4..0a73978c7a 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -38,14 +38,14 @@ jobs:
GOPROXY: https://proxy.golang.org
JDK_VER: ${{ matrix.java }}
DAPR_CLI_VER: 1.15.0
- DAPR_RUNTIME_VER: 1.16.0-rc.3
+ DAPR_RUNTIME_VER: 1.16.0-rc.5
DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.15.0/install/install.sh
DAPR_CLI_REF:
DAPR_REF:
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up OpenJDK ${{ env.JDK_VER }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ env.JDK_VER }}
@@ -62,14 +62,14 @@ jobs:
with:
go-version: ${{ env.GOVER }}
- name: Checkout Dapr CLI repo to override dapr command.
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
if: env.DAPR_CLI_REF != ''
with:
repository: dapr/cli
ref: ${{ env.DAPR_CLI_REF }}
path: cli
- name: Checkout Dapr repo to override daprd.
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
if: env.DAPR_REF != ''
with:
repository: dapr/dapr
diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml
index bc8742b97b..234825356b 100644
--- a/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml
+++ b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml
@@ -6,7 +6,7 @@
io.dapr.spring
dapr-spring-parent
- 0.16.0-rc-1
+ 1.16.0-rc-1
dapr-spring-boot-autoconfigure
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 f7b3a999ad..139dfc0828 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
@@ -25,8 +25,6 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
-import java.util.Map;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -111,6 +109,18 @@ void shouldOverrideGrpcPortIfExists() {
verify(builder).withPropertyOverride(Properties.GRPC_PORT, String.valueOf(grpcPort));
}
+ @Test
+ @DisplayName("Should override API token if it exists")
+ void shouldOverrideApiTokenIfExists() {
+ String apiToken = "token";
+
+ when(connectionDetails.getApiToken()).thenReturn(apiToken);
+
+ configuration.daprClientBuilder(connectionDetails);
+
+ verify(builder).withPropertyOverride(Properties.API_TOKEN, apiToken);
+ }
+
@Test
@DisplayName("Should override HTTP endpoint in properties if it exists")
void shouldOverrideHttpEndpointInPropertiesIfExists() {
@@ -159,6 +169,18 @@ void shouldOverrideGrpcPortPropertiesIfExists() {
assertThat(result.getValue(Properties.GRPC_PORT)).isEqualTo(grpcPort);
}
+ @Test
+ @DisplayName("Should override API token in properties if it exists")
+ void shouldOverrideApiTokenPropertiesIfExists() {
+ String apiToken = "token";
+
+ when(connectionDetails.getApiToken()).thenReturn(apiToken);
+
+ Properties result = configuration.createPropertiesFromConnectionDetails(connectionDetails);
+
+ assertThat(result.getValue(Properties.API_TOKEN)).isEqualTo(apiToken);
+ }
+
private static class TestDaprClientAutoConfiguration extends DaprClientAutoConfiguration {
private final DaprClientBuilder daprClientBuilder;
diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java
index 7a1781d132..33ea73989f 100644
--- a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java
+++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprClientPropertiesTest.java
@@ -83,7 +83,26 @@ public void shouldMapDaprClientProperties() {
});
});
+ }
+ @Test
+ @DisplayName("Should map DaprClient properties correctly (camelCase)")
+ public void shouldMapDaprClientPropertiesCamelCase() {
+ runner.withSystemProperties(
+ "dapr.client.httpEndpoint=http://localhost",
+ "dapr.client.httpPort=3500",
+ "dapr.client.grpcEndpoint=localhost",
+ "dapr.client.grpcPort=50001"
+ ).run(context -> {
+ DaprClientProperties properties = context.getBean(DaprClientProperties.class);
+ SoftAssertions.assertSoftly(softly -> {
+ softly.assertThat(properties.getGrpcEndpoint()).isEqualTo("localhost");
+ softly.assertThat(properties.getHttpEndpoint()).isEqualTo("http://localhost");
+ softly.assertThat(properties.getHttpPort()).isEqualTo(3500);
+ softly.assertThat(properties.getGrpcPort()).isEqualTo(50001);
+ });
+
+ });
}
@EnableConfigurationProperties(DaprClientProperties.class)
diff --git a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml
index 771047a990..da26fc4117 100644
--- a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml
+++ b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter-test/pom.xml
@@ -6,7 +6,7 @@
io.dapr.spring
dapr-spring-parent
- 0.16.0-rc-1
+ 1.16.0-rc-1
../../pom.xml
diff --git a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml
index 8cd8be1b3d..71fd4442ff 100644
--- a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml
+++ b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml
@@ -6,7 +6,7 @@
io.dapr.spring
dapr-spring-parent
- 0.16.0-rc-1
+ 1.16.0-rc-1
../../pom.xml
diff --git a/dapr-spring/dapr-spring-boot-tests/pom.xml b/dapr-spring/dapr-spring-boot-tests/pom.xml
index f0c5b2d233..b388377c53 100644
--- a/dapr-spring/dapr-spring-boot-tests/pom.xml
+++ b/dapr-spring/dapr-spring-boot-tests/pom.xml
@@ -6,7 +6,7 @@
io.dapr.spring
dapr-spring-parent
- 0.16.0-rc-1
+ 1.16.0-rc-1
dapr-spring-boot-tests
diff --git a/dapr-spring/dapr-spring-data/pom.xml b/dapr-spring/dapr-spring-data/pom.xml
index e7f36664df..6ccdefbb04 100644
--- a/dapr-spring/dapr-spring-data/pom.xml
+++ b/dapr-spring/dapr-spring-data/pom.xml
@@ -6,7 +6,7 @@
io.dapr.spring
dapr-spring-parent
- 0.16.0-rc-1
+ 1.16.0-rc-1
dapr-spring-data
diff --git a/dapr-spring/dapr-spring-messaging/pom.xml b/dapr-spring/dapr-spring-messaging/pom.xml
index 0c2445c642..f84a7927b0 100644
--- a/dapr-spring/dapr-spring-messaging/pom.xml
+++ b/dapr-spring/dapr-spring-messaging/pom.xml
@@ -6,7 +6,7 @@
io.dapr.spring
dapr-spring-parent
- 0.16.0-rc-1
+ 1.16.0-rc-1
dapr-spring-messaging
diff --git a/dapr-spring/dapr-spring-workflows/pom.xml b/dapr-spring/dapr-spring-workflows/pom.xml
index 0c4915a077..39a6777809 100644
--- a/dapr-spring/dapr-spring-workflows/pom.xml
+++ b/dapr-spring/dapr-spring-workflows/pom.xml
@@ -6,7 +6,7 @@
io.dapr.spring
dapr-spring-parent
- 0.16.0-rc-1
+ 1.16.0-rc-1
dapr-spring-workflows
diff --git a/dapr-spring/pom.xml b/dapr-spring/pom.xml
index 5f2c5b8919..f6f31b15f4 100644
--- a/dapr-spring/pom.xml
+++ b/dapr-spring/pom.xml
@@ -13,7 +13,7 @@
io.dapr.spring
dapr-spring-parent
pom
- 0.16.0-rc-1
+ 1.16.0-rc-1
dapr-spring-parent
SDK extension for Spring and Spring Boot
@@ -33,7 +33,6 @@
11
1.19.8
5.11.2
- 0.16.0-rc-1
@@ -57,27 +56,27 @@
io.dapr
dapr-sdk-workflows
- ${dapr.sdk.alpha.version}
+ ${dapr.sdk.version}
io.dapr.spring
dapr-spring-data
- ${dapr.spring.version}
+ ${dapr.sdk.version}
io.dapr.spring
dapr-spring-messaging
- ${dapr.spring.version}
+ ${dapr.sdk.version}
io.dapr.spring
dapr-spring-workflows
- ${dapr.spring.version}
+ ${dapr.sdk.version}
io.dapr.spring
dapr-spring-boot-autoconfigure
- ${dapr.spring.version}
+ ${dapr.sdk.version}
io.dapr
@@ -119,7 +118,7 @@
io.dapr.spring
dapr-spring-boot-tests
- ${dapr.spring.version}
+ ${dapr.sdk.version}
@@ -186,6 +185,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
3.2.0
+
+ true
+
attach-javadocs
diff --git a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md
index 1c12aa50c2..ccc365cf42 100644
--- a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md
+++ b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md
@@ -6,7 +6,7 @@ weight: 20000
description: How to get up and running with workflows using the Dapr Java SDK
---
-Let’s create a Dapr workflow and invoke it using the console. With the [provided workflow example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows), you will:
+Let's create a Dapr workflow and invoke it using the console. With the [provided workflow example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows), you will:
- Execute the workflow instance using the [Java workflow worker](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java)
- Utilize the Java workflow client and API calls to [start and terminate workflow instances](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java)
@@ -85,11 +85,10 @@ You're up and running! Both Dapr and your app logs will appear here.
== APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001.
```
-## Run the `DemoWorkflowClient
+## Run the `DemoWorkflowClient`
The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr.
-
```java
public class DemoWorkflowClient {
@@ -246,4 +245,40 @@ Exiting DemoWorkflowClient.
## Next steps
- [Learn more about Dapr workflow]({{% ref workflow-overview.md %}})
-- [Workflow API reference]({{% ref workflow_api.md %}})
\ No newline at end of file
+- [Workflow API reference]({{% ref workflow_api.md %}})
+
+## Advanced features
+
+### Task Execution Keys
+
+Task execution keys are unique identifiers generated by the durabletask-java library. They are stored in the `WorkflowActivityContext` and can be used to track and manage the execution of workflow activities. They are particularly useful for:
+
+1. **Idempotency**: Ensuring activities are not executed multiple times for the same task
+2. **State Management**: Tracking the state of activity execution
+3. **Error Handling**: Managing retries and failures in a controlled manner
+
+Here's an example of how to use task execution keys in your workflow activities:
+
+```java
+public class TaskExecutionKeyActivity implements WorkflowActivity {
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ // Get the task execution key for this activity
+ String taskExecutionKey = ctx.getTaskExecutionKey();
+
+ // Use the key to implement idempotency or state management
+ // For example, check if this task has already been executed
+ if (isTaskAlreadyExecuted(taskExecutionKey)) {
+ return getPreviousResult(taskExecutionKey);
+ }
+
+ // Execute the activity logic
+ Object result = executeActivityLogic();
+
+ // Store the result with the task execution key
+ storeResult(taskExecutionKey, result);
+
+ return result;
+ }
+}
+```
diff --git a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md
index b530d06cd3..c6bb4605ef 100644
--- a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md
+++ b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md
@@ -56,13 +56,13 @@ This will connect to the default Dapr gRPC endpoint `localhost:50001`, requiring
{{% alert title="Note" color="primary" %}}
By default, the following properties are preconfigured for `DaprClient` and `DaprWorkflowClient`:
```properties
-dapr.client.http-endpoint=http://localhost
-dapr.client.http-port=3500
-dapr.client.grpc-endpoint=localhost
-dapr.client.grpc-port=50001
-dapr.client.api-token=
+dapr.client.httpEndpoint=http://localhost
+dapr.client.httpPort=3500
+dapr.client.grpcEndpoint=localhost
+dapr.client.grpcPort=50001
+dapr.client.apiToken=
```
-These values are used by default, but you can override them in your `application.properties` file to suit your environment.
+These values are used by default, but you can override them in your `application.properties` file to suit your environment. Please note that both kebab case and camel case are supported.
{{% /alert %}}
You can use the `DaprClient` to interact with the Dapr APIs anywhere in your application, for example from inside a REST endpoint:
@@ -95,7 +95,7 @@ public class DaprTestContainersConfig {
@ServiceConnection
public DaprContainer daprContainer(Network daprNetwork, PostgreSQLContainer> postgreSQLContainer){
- return new DaprContainer("daprio/daprd:1.16.0-rc.3")
+ return new DaprContainer("daprio/daprd:1.16.0-rc.5")
.withAppName("producer-app")
.withNetwork(daprNetwork)
.withComponent(new Component("kvstore", "state.postgresql", "v1", STATE_STORE_PROPERTIES))
@@ -250,7 +250,7 @@ Finally, because Dapr PubSub requires a bidirectional connection between your ap
@ServiceConnection
public DaprContainer daprContainer(Network daprNetwork, PostgreSQLContainer> postgreSQLContainer, RabbitMQContainer rabbitMQContainer){
- return new DaprContainer("daprio/daprd:1.16.0-rc.3")
+ return new DaprContainer("daprio/daprd:1.16.0-rc.5")
.withAppName("producer-app")
.withNetwork(daprNetwork)
.withComponent(new Component("kvstore", "state.postgresql", "v1", STATE_STORE_PROPERTIES))
diff --git a/examples/pom.xml b/examples/pom.xml
index f9a52382f5..fb42dd1482 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -127,7 +127,7 @@
io.dapr
dapr-sdk-workflows
- ${dapr.sdk.alpha.version}
+ ${dapr.sdk.version}
io.dapr
diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md
index 109e29ced9..6cede9c010 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/README.md
+++ b/examples/src/main/java/io/dapr/examples/workflows/README.md
@@ -53,6 +53,7 @@ Those examples contain the following workflow patterns:
4. [External Event Pattern](#external-event-pattern)
5. [Child-workflow Pattern](#child-workflow-pattern)
6. [Compensation Pattern](#compensation-pattern)
+7. [Cross-App Pattern](#cross-app-pattern)
### Chaining Pattern
In the chaining pattern, a sequence of activities executes in a specific order.
@@ -147,7 +148,7 @@ background: true
-->
Execute the following script in order to run DemoChainWorker:
```sh
-dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainWorker 50001
+dapr run --app-id chainingworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainWorker 50001
```
Once running, the logs will start displaying the different steps: First, you can see workflow is starting:
@@ -169,7 +170,8 @@ timeout_seconds: 20
-->
Then, execute the following script in order to run DemoChainClient:
```sh
-sleep 10 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainClient 50001
+java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.chain.DemoChainClient 50001
+dapr stop --app-id chainingworker
```
@@ -266,7 +268,7 @@ background: true
Execute the following script in order to run DemoFanInOutWorker:
```sh
-dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutWorker 50002
+dapr run --app-id faninoutworker --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutWorker 50002
```
@@ -282,7 +284,8 @@ timeout_seconds: 20
Execute the following script in order to run DemoFanInOutClient:
```sh
-sleep 10 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutClient 50002
+java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.faninout.DemoFanInOutClient 50002
+dapr stop --app-id faninoutworker
```
@@ -660,7 +663,8 @@ timeout_seconds: 30
-->
Once running, execute the following script to run the BookTripClient:
```sh
-sleep 15 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.compensation.BookTripClient 50003
+java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.compensation.BookTripClient 50003
+dapr stop --app-id book-trip-worker
```
@@ -678,6 +682,158 @@ Key Points:
4. Each activity simulates work with a short delay for demonstration purposes
+### Cross-App Pattern
+
+The cross-app pattern allows workflows to call activities that are hosted in different Dapr applications. This is useful for microservices architectures allowing multiple applications to host activities that can be orchestrated by Dapr Workflows.
+
+The `CrossAppWorkflow` class defines the workflow. It demonstrates calling activities in different apps using the `appId` parameter in `WorkflowTaskOptions`. See the code snippet below:
+```java
+public class CrossAppWorkflow implements Workflow {
+ @Override
+ public WorkflowStub create() {
+ return ctx -> {
+ var logger = ctx.getLogger();
+ logger.info("=== WORKFLOW STARTING ===");
+ logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
+ logger.info("Workflow name: {}", ctx.getName());
+ logger.info("Workflow instance ID: {}", ctx.getInstanceId());
+
+ String input = ctx.getInput(String.class);
+ logger.info("CrossAppWorkflow received input: {}", input);
+ logger.info("Workflow input: {}", input);
+
+ // Call an activity in another app by passing in an active appID to the WorkflowTaskOptions
+ logger.info("Calling cross-app activity in 'app2'...");
+ logger.info("About to call cross-app activity in app2...");
+ String crossAppResult = ctx.callActivity(
+ App2TransformActivity.class.getName(),
+ input,
+ new WorkflowTaskOptions("app2"),
+ String.class
+ ).await();
+
+ // Call another activity in a different app
+ logger.info("Calling cross-app activity in 'app3'...");
+ logger.info("About to call cross-app activity in app3...");
+ String finalResult = ctx.callActivity(
+ App3FinalizeActivity.class.getName(),
+ crossAppResult,
+ new WorkflowTaskOptions("app3"),
+ String.class
+ ).await();
+ logger.info("Final cross-app activity result: {}", finalResult);
+ logger.info("Final cross-app activity result: {}", finalResult);
+
+ logger.info("CrossAppWorkflow finished with: {}", finalResult);
+ logger.info("=== WORKFLOW COMPLETING WITH: {} ===" , finalResult);
+ ctx.complete(finalResult);
+ };
+ }
+}
+
+```
+
+The `App2TransformActivity` class defines an activity in app2 that transforms the input string. See the code snippet below:
+```java
+public class App2TransformActivity implements WorkflowActivity {
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ var logger = ctx.getLogger();
+ logger.info("=== App2: TransformActivity called ===");
+ String input = ctx.getInput(String.class);
+ logger.info("Input: {}", input);
+
+ // Transform the input
+ String result = input.toUpperCase() + " [TRANSFORMED BY APP2]";
+
+ logger.info("Output: {}", result);
+ return result;
+ }
+}
+```
+
+The `App3FinalizeActivity` class defines an activity in app3 that finalizes the processing. See the code snippet below:
+```java
+public class App3FinalizeActivity implements WorkflowActivity {
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ var logger = ctx.getLogger();
+ logger.info("=== App3: FinalizeActivity called ===");
+ String input = ctx.getInput(String.class);
+ logger.info("Input: ", input);
+
+ // Finalize the processing
+ String result = input + " [FINALIZED BY APP3]";
+
+ logger.info("Output: {}", result);
+ return result;
+ }
+}
+```
+
+**Key Features:**
+- **Cross-app activity calls**: Call activities in different Dapr applications specifying the appID in the WorkflowTaskOptions
+- **WorkflowTaskOptions with appId**: Specify which app should handle the activity
+- **Combined with retry policies**: Use app ID along with retry policies and handlers
+- **Error handling**: Works the same as local activity calls
+
+**Requirements:**
+- Multiple Dapr applications running with different app IDs
+- Activities registered in the target applications
+- Proper Dapr workflow runtime configuration
+
+**Important Limitations:**
+- **Cross-app calls are currently supported for activities only**
+- **Child workflow cross-app calls (suborchestration) are NOT supported**
+- The app ID must match the Dapr application ID of the target service
+
+**Running the Cross-App Example:**
+
+This example requires running multiple Dapr applications simultaneously. You'll need to run the following commands in separate terminals:
+
+1. **Start the main workflow worker (crossapp-worker):**
+```sh
+dapr run --app-id crossapp-worker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorker
+```
+
+2. **Start app2 worker (handles App2TransformActivity):**
+```sh
+dapr run --app-id app2 --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App2Worker
+```
+
+3. **Start app3 worker (handles App3FinalizeActivity):**
+```sh
+dapr run --app-id app3 --resources-path ./components/workflows --dapr-grpc-port 50003 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App3Worker
+```
+
+4. **Run the workflow client:**
+```sh
+java -Djava.util.logging.ConsoleHandler.level=FINE -Dio.dapr.durabletask.level=FINE -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorkflowClient "Hello World"
+```
+
+**Expected Output:**
+
+The client will show:
+```text
+=== Starting Cross-App Workflow Client ===
+Input: Hello World
+Created DaprWorkflowClient successfully
+Attempting to start new workflow...
+Started a new cross-app workflow with instance ID: 001113f3-b9d9-438c-932a-a9a9b70b9460
+Waiting for workflow completion...
+Workflow instance with ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 completed with result: HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3]
+```
+
+The workflow demonstrates:
+1. The workflow starts in the main worker (crossapp-worker)
+2. Calls an activity in 'app2' using cross-app functionality
+3. Calls an activity in 'app3' using cross-app functionality
+4. The workflow completes with the final result from all activities
+
+This pattern is particularly useful for:
+- Microservices architectures where activities are distributed across multiple services
+- Multi-tenant applications where activities are isolated by app ID
+
### Suspend/Resume Pattern
Workflow instances can be suspended and resumed. This example shows how to use the suspend and resume commands.
@@ -702,7 +858,7 @@ timeout_seconds: 30
-->
```sh
-dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50004 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeWorker 50004
+dapr run --app-id suspendresumeworker --resources-path ./components/workflows --dapr-grpc-port 50004 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeWorker 50004
```
@@ -720,7 +876,8 @@ expected_stdout_lines:
timeout_seconds: 30
-->
```sh
-sleep 15 && java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeClient 50004
+java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.suspendresume.DemoSuspendResumeClient 50004
+dapr stop --app-id suspendresumeworker
```
diff --git a/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java b/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java
index 6c9eedbdc6..334e40f8df 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/chain/DemoChainClient.java
@@ -14,9 +14,11 @@
package io.dapr.examples.workflows.chain;
import io.dapr.examples.workflows.utils.PropertyUtils;
+import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
+import java.time.Duration;
import java.util.concurrent.TimeoutException;
public class DemoChainClient {
@@ -28,15 +30,15 @@ public class DemoChainClient {
*/
public static void main(String[] args) {
try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) {
- String instanceId = client.scheduleNewWorkflow(DemoChainWorkflow.class);
+ String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(DemoChainWorkflow.class),
+ Duration.ofSeconds(60));
+
System.out.printf("Started a new chaining model workflow with instance ID: %s%n", instanceId);
WorkflowInstanceStatus workflowInstanceStatus =
client.waitForInstanceCompletion(instanceId, null, true);
String result = workflowInstanceStatus.readOutputAs(String.class);
System.out.printf("workflow instance with ID: %s completed with result: %s%n", instanceId, result);
-
-
} catch (TimeoutException | InterruptedException e) {
throw new RuntimeException(e);
}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java b/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java
index ce76d5de11..b7c4760e52 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/compensation/BookTripClient.java
@@ -14,6 +14,7 @@
package io.dapr.examples.workflows.compensation;
import io.dapr.examples.workflows.utils.PropertyUtils;
+import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
@@ -23,7 +24,7 @@
public class BookTripClient {
public static void main(String[] args) {
try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) {
- String instanceId = client.scheduleNewWorkflow(BookTripWorkflow.class);
+ String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(BookTripWorkflow.class), Duration.ofSeconds(60));
System.out.printf("Started a new trip booking workflow with instance ID: %s%n", instanceId);
WorkflowInstanceStatus status = client.waitForInstanceCompletion(instanceId, Duration.ofMinutes(30), true);
diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java
new file mode 100644
index 0000000000..62db807d18
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2TransformActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.examples.workflows.crossapp;
+
+import io.dapr.workflows.WorkflowActivity;
+import io.dapr.workflows.WorkflowActivityContext;
+
+/**
+ * TransformActivity for App2 - transforms input to uppercase.
+ * This activity is called cross-app from the main workflow.
+ */
+public class App2TransformActivity implements WorkflowActivity {
+ @Override
+ public Object run(WorkflowActivityContext context) {
+ String input = context.getInput(String.class);
+ var logger = context.getLogger();
+ logger.info("=== App2: TransformActivity called ===");
+ logger.info("Input: {}", input);
+ String result = input.toUpperCase() + " [TRANSFORMED BY APP2]";
+ logger.info("Output: {}", result);
+ return result;
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java
new file mode 100644
index 0000000000..8bad2b8f5b
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App2Worker.java
@@ -0,0 +1,37 @@
+/*
+ * 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.examples.workflows.crossapp;
+
+import io.dapr.workflows.runtime.WorkflowRuntime;
+import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
+
+/**
+ * App2 Worker - registers only the TransformActivity.
+ * This app will handle cross-app activity calls from the main workflow.
+ */
+public class App2Worker {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("=== Starting App2Worker ===");
+ // Register the Workflow with the builder
+ WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
+ .registerActivity(App2TransformActivity.class);
+
+ // Build and start the workflow runtime
+ try (WorkflowRuntime runtime = builder.build()) {
+ System.out.println("App2 is ready to receive cross-app activity calls...");
+ runtime.start();
+ }
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java
new file mode 100644
index 0000000000..0f7c0ddad1
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3FinalizeActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.examples.workflows.crossapp;
+
+import io.dapr.workflows.WorkflowActivity;
+import io.dapr.workflows.WorkflowActivityContext;
+
+/**
+ * FinalizeActivity for App3 - adds final processing.
+ * This activity is called cross-app from the main workflow.
+ */
+public class App3FinalizeActivity implements WorkflowActivity {
+ @Override
+ public Object run(WorkflowActivityContext context) {
+ String input = context.getInput(String.class);
+ var logger = context.getLogger();
+ logger.info("=== App3: FinalizeActivity called ===");
+ logger.info("Input: {}", input);
+ String result = input + " [FINALIZED BY APP3]";
+ logger.info("Output: {}", result);
+ return result;
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java
new file mode 100644
index 0000000000..dc49baa76e
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/App3Worker.java
@@ -0,0 +1,37 @@
+/*
+ * 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.examples.workflows.crossapp;
+
+import io.dapr.workflows.runtime.WorkflowRuntime;
+import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
+
+/**
+ * App3 Worker - registers only the FinalizeActivity.
+ * This app will handle cross-app activity calls from the main workflow.
+ */
+public class App3Worker {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("=== Starting App3Worker ===");
+ // Register the Workflow with the builder
+ WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
+ .registerActivity(App3FinalizeActivity.class);
+
+ // Build and start the workflow runtime
+ try (WorkflowRuntime runtime = builder.build()) {
+ System.out.println("App3 is ready to receive cross-app activity calls...");
+ runtime.start();
+ }
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java
new file mode 100644
index 0000000000..ecf7dfcb6f
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorker.java
@@ -0,0 +1,32 @@
+/*
+ * 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.examples.workflows.crossapp;
+
+import io.dapr.workflows.runtime.WorkflowRuntime;
+import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
+
+public class CrossAppWorker {
+
+ public static void main(String[] args) throws Exception {
+ // Register the Workflow with the builder
+ WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
+ .registerWorkflow(CrossAppWorkflow.class);
+
+ // Build and start the workflow runtime
+ try (WorkflowRuntime runtime = builder.build()) {
+ System.out.println("CrossAppWorker started - registered CrossAppWorkflow only");
+ runtime.start();
+ }
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java
new file mode 100644
index 0000000000..d7eeb9d88e
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflow.java
@@ -0,0 +1,64 @@
+/*
+ * 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.examples.workflows.crossapp;
+
+import io.dapr.workflows.Workflow;
+import io.dapr.workflows.WorkflowStub;
+import io.dapr.workflows.WorkflowTaskOptions;
+
+/**
+ * Example workflow that demonstrates cross-app activity calls.
+ * This workflow calls activities in different apps using the appId parameter.
+ */
+public class CrossAppWorkflow implements Workflow {
+ @Override
+ public WorkflowStub create() {
+ return ctx -> {
+ var logger = ctx.getLogger();
+ logger.info("=== WORKFLOW STARTING ===");
+ logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
+ logger.info("Workflow name: {}", ctx.getName());
+ logger.info("Workflow instance ID: {}", ctx.getInstanceId());
+
+ String input = ctx.getInput(String.class);
+ logger.info("CrossAppWorkflow received input: {}", input);
+ logger.info("Workflow input: {}", input);
+
+ // Call an activity in another app by passing in an active appID to the WorkflowTaskOptions
+ logger.info("Calling cross-app activity in 'app2'...");
+ logger.info("About to call cross-app activity in app2...");
+ String crossAppResult = ctx.callActivity(
+ App2TransformActivity.class.getName(),
+ input,
+ new WorkflowTaskOptions("app2"),
+ String.class
+ ).await();
+
+ // Call another activity in a different app
+ logger.info("Calling cross-app activity in 'app3'...");
+ logger.info("About to call cross-app activity in app3...");
+ String finalResult = ctx.callActivity(
+ App3FinalizeActivity.class.getName(),
+ crossAppResult,
+ new WorkflowTaskOptions("app3"),
+ String.class
+ ).await();
+ logger.info("Final cross-app activity result: {}", finalResult);
+
+ logger.info("CrossAppWorkflow finished with: {}", finalResult);
+ logger.info("=== WORKFLOW COMPLETING WITH: {} ===", finalResult);
+ ctx.complete(finalResult);
+ };
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java
new file mode 100644
index 0000000000..1e7910cd2c
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/crossapp/CrossAppWorkflowClient.java
@@ -0,0 +1,66 @@
+/*
+ * 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.examples.workflows.crossapp;
+
+import io.dapr.workflows.client.DaprWorkflowClient;
+import io.dapr.workflows.client.WorkflowInstanceStatus;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Cross-App Workflow Client - starts and monitors workflows.
+ *
+ * 1. Create a workflow client
+ * 2. Start a new workflow instance
+ * 3. Wait for completion and get results
+ */
+public class CrossAppWorkflowClient {
+
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.out.println("Usage: CrossAppWorkflowClientExample ");
+ System.out.println("Example: CrossAppWorkflowClientExample \"Hello World\"");
+ return;
+ }
+
+ String input = args[0];
+ System.out.println("=== Starting Cross-App Workflow Client ===");
+ System.out.println("Input: " + input);
+
+ try (DaprWorkflowClient client = new DaprWorkflowClient()) {
+ System.out.println("Created DaprWorkflowClient successfully");
+
+ // Start a new workflow instance
+ System.out.println("Attempting to start new workflow...");
+ String instanceId = client.scheduleNewWorkflow(CrossAppWorkflow.class, input);
+ System.out.printf("Started a new cross-app workflow with instance ID: %s%n", instanceId);
+
+ // Wait for the workflow to complete
+ System.out.println("Waiting for workflow completion...");
+ WorkflowInstanceStatus workflowInstanceStatus =
+ client.waitForInstanceCompletion(instanceId, null, true);
+
+ // Get the result
+ String result = workflowInstanceStatus.readOutputAs(String.class);
+ System.out.printf("Workflow instance with ID: %s completed with result: %s%n", instanceId, result);
+
+ } catch (TimeoutException | InterruptedException e) {
+ System.err.println("Error waiting for workflow completion: " + e.getMessage());
+ e.printStackTrace();
+ } catch (Exception e) {
+ System.err.println("Error creating workflow client or starting workflow: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java b/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java
index 612a8979d3..871b15cfe4 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/faninout/DemoFanInOutClient.java
@@ -14,6 +14,7 @@
package io.dapr.examples.workflows.faninout;
import io.dapr.examples.workflows.utils.PropertyUtils;
+import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
@@ -40,9 +41,10 @@ public static void main(String[] args) throws InterruptedException {
"Always remember that you are absolutely unique. Just like everyone else.");
// Schedule an orchestration which will reliably count the number of words in all the given sentences.
- String instanceId = client.scheduleNewWorkflow(
+ String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(
DemoFanInOutWorkflow.class,
- listOfStrings);
+ listOfStrings), Duration.ofSeconds(60));
+
System.out.printf("Started a new fan out/fan in model workflow with instance ID: %s%n", instanceId);
// Block until the orchestration completes. Then print the final status, which includes the output.
diff --git a/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java
index 5880c64f2d..5b94b5fa5a 100644
--- a/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java
+++ b/examples/src/main/java/io/dapr/examples/workflows/suspendresume/DemoSuspendResumeClient.java
@@ -15,9 +15,11 @@
import io.dapr.examples.workflows.externalevent.DemoExternalEventWorkflow;
import io.dapr.examples.workflows.utils.PropertyUtils;
+import io.dapr.examples.workflows.utils.RetryUtils;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
+import java.time.Duration;
import java.util.concurrent.TimeoutException;
public class DemoSuspendResumeClient {
@@ -29,7 +31,7 @@ public class DemoSuspendResumeClient {
*/
public static void main(String[] args) {
try (DaprWorkflowClient client = new DaprWorkflowClient(PropertyUtils.getProperties(args))) {
- String instanceId = client.scheduleNewWorkflow(DemoExternalEventWorkflow.class);
+ String instanceId = RetryUtils.callWithRetry(() -> client.scheduleNewWorkflow(DemoExternalEventWorkflow.class), Duration.ofSeconds(60));
System.out.printf("Started a new external-event workflow with instance ID: %s%n", instanceId);
diff --git a/examples/src/main/java/io/dapr/examples/workflows/utils/RetryUtils.java b/examples/src/main/java/io/dapr/examples/workflows/utils/RetryUtils.java
new file mode 100644
index 0000000000..42651b06b5
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/workflows/utils/RetryUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.examples.workflows.utils;
+
+import java.time.Duration;
+import java.util.concurrent.Callable;
+
+public class RetryUtils {
+ private static final long RETRY_WAIT_MILLISECONDS = 1000;
+
+ public static String callWithRetry(Callable function, Duration retryTimeout) throws InterruptedException {
+ var retryTimeoutMilliseconds = retryTimeout.toMillis();
+ long started = System.currentTimeMillis();
+ while (true) {
+ Throwable exception;
+ try {
+ return function.call();
+ } catch (Exception | AssertionError e) {
+ exception = e;
+ }
+
+ long elapsed = System.currentTimeMillis() - started;
+ if (elapsed >= retryTimeoutMilliseconds) {
+ if (exception instanceof RuntimeException) {
+ throw (RuntimeException) exception;
+ }
+
+ throw new RuntimeException(exception);
+ }
+
+ long remaining = retryTimeoutMilliseconds - elapsed;
+ Thread.sleep(Math.min(remaining, RETRY_WAIT_MILLISECONDS));
+ }
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 1e6327b7f1..01e39c0cef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,7 @@
UTF-8
1.69.0
3.25.5
- protoc
+ java-sdk-protoc
https://raw.githubusercontent.com/dapr/dapr/v1.16.0-rc.3/dapr/proto
1.16.0-rc-1
0.16.0-rc-1
@@ -70,7 +70,7 @@
ossrh
- https://ossrh-staging-api.central.sonatype.com/content/repositories/snapshots
+ https://central.sonatype.com/repository/maven-snapshots/
localDocsDirectory
@@ -207,12 +207,12 @@
io.dapr.spring
dapr-spring-boot-starter
- ${dapr.sdk.alpha.version}
+ ${dapr.sdk.version}
io.dapr.spring
dapr-spring-boot-starter-test
- ${dapr.sdk.alpha.version}
+ ${dapr.sdk.version}
org.springframework.boot
@@ -247,7 +247,7 @@
io.dapr
testcontainers-dapr
- ${dapr.sdk.alpha.version}
+ ${dapr.sdk.version}
org.testcontainers
@@ -365,7 +365,7 @@
io.dapr
dapr-sdk-workflows
- ${dapr.sdk.alpha.version}
+ ${dapr.sdk.version}
org.wiremock
@@ -610,6 +610,7 @@
false
true
site
+ true
io.dapr.examples:io.dapr.springboot:io.dapr.examples.*:io.dapr.springboot.*
@@ -658,6 +659,18 @@
sdk-tests
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
diff --git a/sdk-actors/pom.xml b/sdk-actors/pom.xml
index d49cd494f6..dc3978ce1a 100644
--- a/sdk-actors/pom.xml
+++ b/sdk-actors/pom.xml
@@ -86,6 +86,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
3.2.0
+
+ true
+
attach-javadocs
diff --git a/sdk-autogen/pom.xml b/sdk-autogen/pom.xml
index 0af30fd9ea..30e8dd9762 100644
--- a/sdk-autogen/pom.xml
+++ b/sdk-autogen/pom.xml
@@ -22,7 +22,7 @@
${project.build.directory}/proto
false
1.69.0
- protoc
+ java-sdk-protoc
3.25.5
@@ -132,7 +132,7 @@
run
- protoc
+ java-sdk-protoc
${protobuf.version}
com.google.protobuf:protoc:3.25.5
inputs
@@ -176,6 +176,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
3.2.0
+
+ true
+
attach-javadocs
diff --git a/sdk-springboot/pom.xml b/sdk-springboot/pom.xml
index a97c048ddf..59984ddd52 100644
--- a/sdk-springboot/pom.xml
+++ b/sdk-springboot/pom.xml
@@ -97,6 +97,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
3.7.0
+
+ true
+
attach-javadocs
diff --git a/sdk-tests/pom.xml b/sdk-tests/pom.xml
index ea8c5d83d1..0ca71bd5e7 100644
--- a/sdk-tests/pom.xml
+++ b/sdk-tests/pom.xml
@@ -22,8 +22,6 @@
17
17
true
- 1.16.0-rc-1
- 0.16.0-rc-1
${project.build.directory}/generated-sources
${project.basedir}/proto
@@ -315,18 +313,6 @@
org.apache.maven.plugins
maven-failsafe-plugin
3.2.2
-
-
-
- integration-test
- verify
-
-
-
- ${skipITs}
-
-
-
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java
index 2301e6fc36..7177d2aab5 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/conversations/DaprConversationIT.java
@@ -109,11 +109,9 @@ public void testConversationSDKShouldScrubPIIWhenScrubPIIIsSetInRequestBody() {
this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)
.setScrubPii(true)).block();
- Assertions.assertEquals("", response.getContextId());
- Assertions.assertEquals("input this ",
+ Assertions.assertEquals("", response.getContextId());
+ Assertions.assertEquals("input this \ninput this ",
response.getConversationOutputs().get(0).getResult());
- Assertions.assertEquals("input this ",
- response.getConversationOutputs().get(1).getResult());
}
@Test
@@ -126,9 +124,7 @@ public void testConversationSDKShouldScrubPIIOnlyForTheInputWhereScrubPIIIsSet()
this.daprPreviewClient.converse(new ConversationRequest("echo", conversationInputList)).block();
Assertions.assertEquals("", response.getContextId());
- Assertions.assertEquals("input this abcd@gmail.com",
+ Assertions.assertEquals("input this abcd@gmail.com\ninput this ",
response.getConversationOutputs().get(0).getResult());
- Assertions.assertEquals("input this ",
- response.getConversationOutputs().get(1).getResult());
}
}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java
index 19fe8f986b..db531d5146 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/DaprWorkflowsIT.java
@@ -15,6 +15,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
import io.dapr.testcontainers.DaprLogLevel;
@@ -41,6 +42,7 @@
import java.util.Map;
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
+import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -147,6 +149,51 @@ public void testSuspendAndResumeWorkflows() throws Exception {
}
+ @Test
+ public void testNamedActivitiesWorkflows() throws Exception {
+ TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>());
+ String instanceId = workflowClient.scheduleNewWorkflow(TestNamedActivitiesWorkflow.class, payload);
+
+ workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false);
+
+ Duration timeout = Duration.ofSeconds(10);
+ WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true);
+
+ assertNotNull(workflowStatus);
+
+ TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput());
+
+ assertEquals(5, workflowOutput.getPayloads().size());
+ assertEquals("First Activity", workflowOutput.getPayloads().get(0));
+ assertEquals("First Activity", workflowOutput.getPayloads().get(1));
+ assertEquals("Second Activity", workflowOutput.getPayloads().get(2));
+ assertEquals("Anonymous Activity", workflowOutput.getPayloads().get(3));
+ assertEquals("Anonymous Activity 2", workflowOutput.getPayloads().get(4));
+
+ assertEquals(instanceId, workflowOutput.getWorkflowId());
+ }
+
+ @Test
+ public void testExecutionKeyWorkflows() throws Exception {
+ TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>());
+ String instanceId = workflowClient.scheduleNewWorkflow(TestExecutionKeysWorkflow.class, payload);
+
+ workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(100), false);
+
+ Duration timeout = Duration.ofSeconds(1000);
+ WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true);
+
+ assertNotNull(workflowStatus);
+
+ TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput());
+
+ assertEquals(1, workflowOutput.getPayloads().size());
+ assertEquals("Execution key found", workflowOutput.getPayloads().get(0));
+
+ assertTrue(KeyStore.getInstance().size() == 1);
+
+ assertEquals(instanceId, workflowOutput.getWorkflowId());
+ }
private TestWorkflowPayload deserialize(String value) throws JsonProcessingException {
return OBJECT_MAPPER.readValue(value, TestWorkflowPayload.class);
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/KeyStore.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/KeyStore.java
new file mode 100644
index 0000000000..1e3f95aae0
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/KeyStore.java
@@ -0,0 +1,55 @@
+/*
+ * 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.it.testcontainers.workflows;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class KeyStore {
+
+ private final Map keyStore = new HashMap<>();
+
+ private static KeyStore instance;
+
+ private KeyStore() {
+ }
+
+ public static KeyStore getInstance() {
+ if (instance == null) {
+ synchronized (KeyStore.class) {
+ if (instance == null) {
+ instance = new KeyStore();
+ }
+ }
+ }
+ return instance;
+ }
+
+
+ public void addKey(String key, Boolean value) {
+ keyStore.put(key, value);
+ }
+
+ public Boolean getKey(String key) {
+ return keyStore.get(key);
+ }
+
+ public void removeKey(String key) {
+ keyStore.remove(key);
+ }
+
+ public int size() {
+ return keyStore.size();
+ }
+
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TaskExecutionIdActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TaskExecutionIdActivity.java
new file mode 100644
index 0000000000..27acb03aeb
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TaskExecutionIdActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.it.testcontainers.workflows;
+
+import io.dapr.workflows.WorkflowActivity;
+import io.dapr.workflows.WorkflowActivityContext;
+
+public class TaskExecutionIdActivity implements WorkflowActivity {
+
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
+ KeyStore keyStore = KeyStore.getInstance();
+ Boolean exists = keyStore.getKey(ctx.getTaskExecutionId());
+ if (!Boolean.TRUE.equals(exists)) {
+ keyStore.addKey(ctx.getTaskExecutionId(), true);
+ workflowPayload.getPayloads().add("Execution key not found");
+ throw new IllegalStateException("Task execution key not found");
+ }
+ workflowPayload.getPayloads().add("Execution key found");
+ return workflowPayload;
+ }
+
+}
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
new file mode 100644
index 0000000000..65eb1047c4
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestExecutionKeysWorkflow.java
@@ -0,0 +1,58 @@
+/*
+ * 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.it.testcontainers.workflows;
+
+import io.dapr.durabletask.Task;
+import io.dapr.workflows.Workflow;
+import io.dapr.workflows.WorkflowStub;
+import io.dapr.workflows.WorkflowTaskOptions;
+import io.dapr.workflows.WorkflowTaskRetryPolicy;
+
+import java.time.Duration;
+
+import org.slf4j.Logger;
+
+public class TestExecutionKeysWorkflow implements Workflow {
+
+ @Override
+ public WorkflowStub create() {
+ return ctx -> {
+
+ Logger logger = ctx.getLogger();
+ String instanceId = ctx.getInstanceId();
+ logger.info("Starting Workflow: " + ctx.getName());
+ logger.info("Instance ID: " + instanceId);
+ logger.info("Current Orchestration Time: " + ctx.getCurrentInstant());
+
+ TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
+ workflowPayload.setWorkflowId(instanceId);
+
+ WorkflowTaskOptions options = new WorkflowTaskOptions(WorkflowTaskRetryPolicy.newBuilder()
+ .setMaxNumberOfAttempts(3)
+ .setFirstRetryInterval(Duration.ofSeconds(1))
+ .setMaxRetryInterval(Duration.ofSeconds(10))
+ .setBackoffCoefficient(2.0)
+ .setRetryTimeout(Duration.ofSeconds(50))
+ .build());
+
+
+ Task t = ctx.callActivity(TaskExecutionIdActivity.class.getName(), workflowPayload, options,TestWorkflowPayload.class);
+
+ TestWorkflowPayload payloadAfterExecution = t.await();
+
+ ctx.complete(payloadAfterExecution);
+ };
+ }
+
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestNamedActivitiesWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestNamedActivitiesWorkflow.java
new file mode 100644
index 0000000000..c5ce2107cd
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestNamedActivitiesWorkflow.java
@@ -0,0 +1,53 @@
+/*
+ * 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.it.testcontainers.workflows;
+
+import io.dapr.workflows.Workflow;
+import io.dapr.workflows.WorkflowStub;
+import org.slf4j.Logger;
+
+public class TestNamedActivitiesWorkflow implements Workflow {
+
+ @Override
+ public WorkflowStub create() {
+ return ctx -> {
+ Logger logger = ctx.getLogger();
+ String instanceId = ctx.getInstanceId();
+ logger.info("Starting Workflow: " + ctx.getName());
+ logger.info("Instance ID: " + instanceId);
+ logger.info("Current Orchestration Time: " + ctx.getCurrentInstant());
+
+ TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
+ workflowPayload.setWorkflowId(instanceId);
+
+ var payloadAfterA = ctx.callActivity("a", workflowPayload, TestWorkflowPayload.class)
+ .await();
+
+ var payloadAfterB = ctx.callActivity("b", payloadAfterA, TestWorkflowPayload.class)
+ .await();
+
+ var payloadAfterC = ctx.callActivity("c", payloadAfterB, TestWorkflowPayload.class)
+ .await();
+
+ var payloadAfterD = ctx.callActivity("d", payloadAfterC, TestWorkflowPayload.class)
+ .await();
+
+ var payloadAfterE = ctx.callActivity("e", payloadAfterD, TestWorkflowPayload.class)
+ .await();
+
+ ctx.complete(payloadAfterE);
+ };
+ }
+
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestWorkflowsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestWorkflowsConfiguration.java
index 78f749e9dd..3ba555825e 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestWorkflowsConfiguration.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/TestWorkflowsConfiguration.java
@@ -15,8 +15,10 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.config.Properties;
+import io.dapr.workflows.WorkflowActivityContext;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
+import io.dapr.workflows.WorkflowActivity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -48,17 +50,41 @@ public WorkflowRuntimeBuilder workflowRuntimeBuilder(
@Value("${dapr.http.endpoint}") String daprHttpEndpoint,
@Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint
){
- Map overrides = Map.of(
- "dapr.http.endpoint", daprHttpEndpoint,
- "dapr.grpc.endpoint", daprGrpcEndpoint
- );
+ Map overrides = Map.of(
+ "dapr.http.endpoint", daprHttpEndpoint,
+ "dapr.grpc.endpoint", daprGrpcEndpoint
+ );
+
+ WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides));
+
+ builder.registerWorkflow(TestWorkflow.class);
+ builder.registerWorkflow(TestExecutionKeysWorkflow.class);
+ builder.registerWorkflow(TestNamedActivitiesWorkflow.class);
- WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides));
+ builder.registerActivity(FirstActivity.class);
+ builder.registerActivity(SecondActivity.class);
+ builder.registerActivity(TaskExecutionIdActivity.class);
- builder.registerWorkflow(TestWorkflow.class);
- builder.registerActivity(FirstActivity.class);
- builder.registerActivity(SecondActivity.class);
+ builder.registerActivity("a", FirstActivity.class);
+ builder.registerActivity("b", FirstActivity.class);
+ builder.registerActivity("c", new SecondActivity());
+ builder.registerActivity("d", new WorkflowActivity() {
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
+ workflowPayload.getPayloads().add("Anonymous Activity");
+ return workflowPayload;
+ }
+ });
+ builder.registerActivity("e", new WorkflowActivity() {
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
+ workflowPayload.getPayloads().add("Anonymous Activity 2");
+ return workflowPayload;
+ }
+ });
- return builder;
+ return builder;
}
}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java
new file mode 100644
index 0000000000..653e14b59f
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2TransformActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.it.testcontainers.workflows.crossapp;
+
+import io.dapr.workflows.WorkflowActivity;
+import io.dapr.workflows.WorkflowActivityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class App2TransformActivity implements WorkflowActivity {
+
+ private static final Logger logger = LoggerFactory.getLogger(App2TransformActivity.class);
+
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ String input = ctx.getInput(String.class);
+ logger.info("=== App2: TransformActivity called ===");
+ logger.info("Input: {}", input);
+
+ String output = input.toUpperCase() + " [TRANSFORMED BY APP2]";
+ logger.info("Output: {}", output);
+
+ return output;
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java
new file mode 100644
index 0000000000..f9748f302f
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App2Worker.java
@@ -0,0 +1,40 @@
+/*
+ * 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.it.testcontainers.workflows.crossapp;
+
+import io.dapr.workflows.runtime.WorkflowRuntime;
+import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
+
+/**
+ * App2Worker - registers the App2TransformActivity.
+ * This app will handle cross-app activity calls from the main workflow.
+ */
+public class App2Worker {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("=== Starting App2Worker (App2TransformActivity) ===");
+
+ // Register the Activity with the builder
+ WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
+ .registerActivity(App2TransformActivity.class);
+
+ // Build and start the workflow runtime
+ try (WorkflowRuntime runtime = builder.build()) {
+ System.out.println("App2Worker started - registered App2TransformActivity only");
+ System.out.println("App2 is ready to receive cross-app activity calls...");
+ System.out.println("Waiting for cross-app activity calls...");
+ runtime.start();
+ }
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java
new file mode 100644
index 0000000000..106db9a1fb
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3FinalizeActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.it.testcontainers.workflows.crossapp;
+
+import io.dapr.workflows.WorkflowActivity;
+import io.dapr.workflows.WorkflowActivityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class App3FinalizeActivity implements WorkflowActivity {
+
+ private static final Logger logger = LoggerFactory.getLogger(App3FinalizeActivity.class);
+
+ @Override
+ public Object run(WorkflowActivityContext ctx) {
+ String input = ctx.getInput(String.class);
+ logger.info("=== App3: FinalizeActivity called ===");
+ logger.info("Input: {}", input);
+
+ String output = input + " [FINALIZED BY APP3]";
+ logger.info("Output: {}", output);
+
+ return output;
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java
new file mode 100644
index 0000000000..be1d8de468
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/App3Worker.java
@@ -0,0 +1,40 @@
+/*
+ * 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.it.testcontainers.workflows.crossapp;
+
+import io.dapr.workflows.runtime.WorkflowRuntime;
+import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
+
+/**
+ * App3Worker - registers the App3FinalizeActivity.
+ * This app will handle cross-app activity calls from the main workflow.
+ */
+public class App3Worker {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("=== Starting App3Worker (App3FinalizeActivity) ===");
+
+ // Register the Activity with the builder
+ WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
+ .registerActivity(App3FinalizeActivity.class);
+
+ // Build and start the workflow runtime
+ try (WorkflowRuntime runtime = builder.build()) {
+ System.out.println("App3Worker started - registered App3FinalizeActivity only");
+ System.out.println("App3 is ready to receive cross-app activity calls...");
+ System.out.println("Waiting for cross-app activity calls...");
+ runtime.start();
+ }
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java
new file mode 100644
index 0000000000..36a2fdf791
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorker.java
@@ -0,0 +1,40 @@
+/*
+ * 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.it.testcontainers.workflows.crossapp;
+
+import io.dapr.workflows.runtime.WorkflowRuntime;
+import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
+
+/**
+ * CrossAppWorker - registers only the CrossAppWorkflow.
+ * This is the main workflow orchestrator that will call activities in other apps.
+ */
+public class CrossAppWorker {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("=== Starting CrossAppWorker (Workflow Orchestrator) ===");
+
+ // Register the Workflow with the builder
+ WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
+ .registerWorkflow(CrossAppWorkflow.class);
+
+ // Build and start the workflow runtime
+ try (WorkflowRuntime runtime = builder.build()) {
+ System.out.println("CrossAppWorker started - registered CrossAppWorkflow only");
+ System.out.println("Waiting for workflow orchestration requests...");
+ runtime.start();
+ }
+ }
+}
+
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java
new file mode 100644
index 0000000000..806408621e
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/CrossAppWorkflow.java
@@ -0,0 +1,55 @@
+/*
+ * 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.it.testcontainers.workflows.crossapp;
+
+import io.dapr.workflows.Workflow;
+import io.dapr.workflows.WorkflowStub;
+import io.dapr.workflows.WorkflowTaskOptions;
+import org.slf4j.Logger;
+
+public class CrossAppWorkflow implements Workflow {
+ @Override
+ public WorkflowStub create() {
+ return ctx -> {
+ Logger logger = ctx.getLogger();
+ String instanceId = ctx.getInstanceId();
+ logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
+ logger.info("Instance ID: {}", instanceId);
+
+ String input = ctx.getInput(String.class);
+ logger.info("Workflow input: {}", input);
+
+ // Call App2TransformActivity in app2
+ logger.info("Calling cross-app activity in 'app2'...");
+ String transformedByApp2 = ctx.callActivity(
+ App2TransformActivity.class.getName(),
+ input,
+ new WorkflowTaskOptions("app2"),
+ String.class
+ ).await();
+
+ // Call App3FinalizeActivity in app3
+ logger.info("Calling cross-app activity in 'app3'...");
+ String finalizedByApp3 = ctx.callActivity(
+ App3FinalizeActivity.class.getName(),
+ transformedByApp2,
+ new WorkflowTaskOptions("app3"),
+ String.class
+ ).await();
+
+ logger.info("Final cross-app activity result: {}", finalizedByApp3);
+ ctx.complete(finalizedByApp3);
+ };
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java
new file mode 100644
index 0000000000..868d0adec1
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/workflows/crossapp/WorkflowsCrossAppCallActivityIT.java
@@ -0,0 +1,191 @@
+/*
+ * 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.it.testcontainers.workflows.crossapp;
+
+import io.dapr.testcontainers.Component;
+import io.dapr.testcontainers.DaprContainer;
+import io.dapr.testcontainers.DaprLogLevel;
+import io.dapr.testcontainers.DaprPlacementContainer;
+import io.dapr.testcontainers.DaprSchedulerContainer;
+import io.dapr.workflows.client.DaprWorkflowClient;
+import io.dapr.workflows.client.WorkflowInstanceStatus;
+import io.dapr.workflows.client.WorkflowRuntimeStatus;
+import io.dapr.config.Properties;
+import net.bytebuddy.utility.dispatcher.JavaDispatcher;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.Network;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import java.time.Duration;
+import java.util.Map;
+
+import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
+import static io.dapr.testcontainers.DaprContainerConstants.DAPR_PLACEMENT_IMAGE_TAG;
+import static io.dapr.testcontainers.DaprContainerConstants.DAPR_SCHEDULER_IMAGE_TAG;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Cross-App Pattern integration test.
+ *
+ * This test demonstrates the cross-app pattern by:
+ * 1. Starting 3 Dapr containers (crossapp-worker, app2, app3)
+ * 2. Launching Java processes that register workflows/activities in separate apps
+ * 3. Executing a cross-app workflow
+ * 4. Asserting successful completion
+ */
+@Testcontainers
+@Tag("testcontainers")
+public class WorkflowsCrossAppCallActivityIT {
+
+ private static final Network DAPR_NETWORK = Network.newNetwork();
+
+ @Container
+ private final static DaprPlacementContainer sharedPlacementContainer = new DaprPlacementContainer(DAPR_PLACEMENT_IMAGE_TAG)
+ .withNetwork(DAPR_NETWORK)
+ .withNetworkAliases("placement")
+ .withReuse(false);
+
+ @Container
+ private final static DaprSchedulerContainer sharedSchedulerContainer = new DaprSchedulerContainer(DAPR_SCHEDULER_IMAGE_TAG)
+ .withNetwork(DAPR_NETWORK)
+ .withNetworkAliases("scheduler")
+ .withReuse(false);
+
+ // Main workflow orchestrator container
+ @Container
+ private final static DaprContainer MAIN_WORKFLOW_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
+ .withAppName("crossapp-worker")
+ .withNetwork(DAPR_NETWORK)
+ .withNetworkAliases("main-workflow-sidecar")
+ .withPlacementContainer(sharedPlacementContainer)
+ .withSchedulerContainer(sharedSchedulerContainer)
+ .withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true")))
+ .withDaprLogLevel(DaprLogLevel.DEBUG)
+ .dependsOn(sharedPlacementContainer, sharedSchedulerContainer)
+ .withLogConsumer(outputFrame -> System.out.println("MAIN_WORKFLOW: " + outputFrame.getUtf8String()))
+ .withAppChannelAddress("host.testcontainers.internal");
+
+ // App2 container for App2TransformActivity
+ @Container
+ private final static DaprContainer APP2_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
+ .withAppName("app2")
+ .withNetwork(DAPR_NETWORK)
+ .withNetworkAliases("app2-sidecar")
+ .withPlacementContainer(sharedPlacementContainer)
+ .withSchedulerContainer(sharedSchedulerContainer)
+ .withAppChannelAddress("main-workflow-sidecar:3500")
+ .withDaprLogLevel(DaprLogLevel.DEBUG)
+ .dependsOn(sharedPlacementContainer, sharedSchedulerContainer, MAIN_WORKFLOW_SIDECAR)
+ .withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true")))
+ .withLogConsumer(outputFrame -> System.out.println("APP2: " + outputFrame.getUtf8String()));
+
+ // App3 container for App3FinalizeActivity
+ @Container
+ private final static DaprContainer APP3_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
+ .withAppName("app3")
+ .withNetwork(DAPR_NETWORK)
+ .withNetworkAliases("app3-sidecar")
+ .withPlacementContainer(sharedPlacementContainer)
+ .withSchedulerContainer(sharedSchedulerContainer)
+ .withAppChannelAddress("main-workflow-sidecar:3500")
+ .withDaprLogLevel(DaprLogLevel.DEBUG)
+ .dependsOn(sharedPlacementContainer, sharedSchedulerContainer, MAIN_WORKFLOW_SIDECAR)
+ .withComponent(new Component("kvstore", "state.in-memory", "v1", Map.of("actorStateStore", "true")))
+ .withLogConsumer(outputFrame -> System.out.println("APP3: " + outputFrame.getUtf8String()));
+
+
+ // TestContainers for each app
+ @Container
+ private static GenericContainer> crossappWorker = new GenericContainer<>("openjdk:17-jdk-slim")
+ .withCopyFileToContainer(MountableFile.forHostPath("target"), "/app")
+ .withWorkingDirectory("/app")
+ .withCommand("java", "-cp", "test-classes:classes:dependency/*:*",
+ "-Ddapr.app.id=crossapp-worker",
+ "-Ddapr.grpc.endpoint=main-workflow-sidecar:50001",
+ "-Ddapr.http.endpoint=main-workflow-sidecar:3500",
+ "io.dapr.it.testcontainers.workflows.crossapp.CrossAppWorker")
+ .withNetwork(DAPR_NETWORK)
+ .dependsOn(MAIN_WORKFLOW_SIDECAR)
+ .waitingFor(Wait.forLogMessage(".*CrossAppWorker started.*", 1))
+ .withLogConsumer(outputFrame -> System.out.println("CrossAppWorker: " + outputFrame.getUtf8String()));
+
+ @Container
+ private final static GenericContainer> app2Worker = new GenericContainer<>("openjdk:17-jdk-slim")
+ .withCopyFileToContainer(MountableFile.forHostPath("target"), "/app")
+ .withWorkingDirectory("/app")
+ .withCommand("java", "-cp", "test-classes:classes:dependency/*:*",
+ "-Ddapr.app.id=app2",
+ "-Ddapr.grpc.endpoint=app2-sidecar:50001",
+ "-Ddapr.http.endpoint=app2-sidecar:3500",
+ "io.dapr.it.testcontainers.workflows.crossapp.App2Worker")
+ .withNetwork(DAPR_NETWORK)
+ .dependsOn(APP2_SIDECAR)
+ .waitingFor(Wait.forLogMessage(".*App2Worker started.*", 1))
+ .withLogConsumer(outputFrame -> System.out.println("App2Worker: " + outputFrame.getUtf8String()));
+
+ @Container
+ private final static GenericContainer> app3Worker = new GenericContainer<>("openjdk:17-jdk-slim")
+ .withCopyFileToContainer(MountableFile.forHostPath("target"), "/app")
+ .withWorkingDirectory("/app")
+ .withCommand("java", "-cp", "test-classes:classes:dependency/*:*",
+ "-Ddapr.app.id=app3",
+ "-Ddapr.grpc.endpoint=app3-sidecar:50001",
+ "-Ddapr.http.endpoint=app3-sidecar:3500",
+ "io.dapr.it.testcontainers.workflows.crossapp.App3Worker")
+ .withNetwork(DAPR_NETWORK)
+ .dependsOn(APP3_SIDECAR)
+ .waitingFor(Wait.forLogMessage(".*App3Worker started.*", 1))
+ .withLogConsumer(outputFrame -> System.out.println("App3Worker: " + outputFrame.getUtf8String()));
+
+ @Test
+ public void testCrossAppWorkflow() throws Exception {
+ // TestContainers wait strategies ensure all containers are ready before this test runs
+
+ String input = "Hello World";
+ String expectedOutput = "HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3]";
+
+ // Create workflow client connected to the main workflow orchestrator
+ // Use the same endpoint configuration that the workers use
+ // The workers use host.testcontainers.internal:50001
+ Map propertyOverrides = Map.of(
+ "dapr.grpc.endpoint", MAIN_WORKFLOW_SIDECAR.getGrpcEndpoint(),
+ "dapr.http.endpoint", MAIN_WORKFLOW_SIDECAR.getHttpEndpoint()
+ );
+
+ Properties clientProperties = new Properties(propertyOverrides);
+ DaprWorkflowClient workflowClient = new DaprWorkflowClient(clientProperties);
+
+ try {
+ String instanceId = workflowClient.scheduleNewWorkflow(CrossAppWorkflow.class, input);
+ assertNotNull(instanceId, "Workflow instance ID should not be null");
+ workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(30), false);
+
+ WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, null, true);
+ assertNotNull(workflowStatus, "Workflow status should not be null");
+ assertEquals(WorkflowRuntimeStatus.COMPLETED, workflowStatus.getRuntimeStatus(),
+ "Workflow should complete successfully");
+ String workflowOutput = workflowStatus.readOutputAs(String.class);
+ assertEquals(expectedOutput, workflowOutput, "Workflow output should match expected result");
+ } finally {
+ workflowClient.close();
+ }
+ }
+
+}
diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml
index d33cdb4fee..83d3add6a5 100644
--- a/sdk-workflows/pom.xml
+++ b/sdk-workflows/pom.xml
@@ -12,7 +12,7 @@
dapr-sdk-workflows
jar
- 0.16.0-rc-1
+ 1.16.0-rc-1
dapr-sdk-workflows
SDK for Workflows on Dapr
@@ -47,7 +47,7 @@
io.dapr
durabletask-client
- 1.5.6
+ 1.5.10
@@ -73,7 +72,7 @@ timeout_seconds: 90
To start the workflow with the three chained activities you can run:
```sh
-sleep 35 && curl -X POST localhost:8080/wfp/chain -H 'Content-Type: application/json'
+curl -X POST localhost:8080/wfp/chain -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -142,7 +141,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '!wolfkroW rpaD olleH'
-background: true
timeout_seconds: 90
-->
@@ -150,7 +148,7 @@ timeout_seconds: 90
To start the workflow with the three chained activities you can run:
```sh
-sleep 35 && curl -X POST localhost:8080/wfp/child -H 'Content-Type: application/json'
+curl -X POST localhost:8080/wfp/child -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -191,13 +189,12 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"cleanUpTimes":5}'
-background: true
timeout_seconds: 90
-->
```sh
-sleep 30 && curl -X POST localhost:8080/wfp/continueasnew -H 'Content-Type: application/json'
+curl -X POST localhost:8080/wfp/continueasnew -H 'Content-Type: application/json' --retry 10 --max-time 60 --retry-all-errors --retry-max-time 90
```
@@ -260,13 +257,12 @@ To start the workflow you can run:
name: Start External Event Workflow
match_order: none
output_match_mode: substring
-background: true
timeout_seconds: 90
-->
```sh
-sleep 30 && curl -X POST "localhost:8080/wfp/externalevent?orderId=123" -H 'Content-Type: application/json'
+curl -X POST "localhost:8080/wfp/externalevent?orderId=123" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -290,7 +286,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"approved":true}'
-background: true
timeout_seconds: 90
-->
@@ -298,7 +293,7 @@ timeout_seconds: 90
To send the event you can run:
```sh
-sleep 42 && curl -X POST "localhost:8080/wfp/externalevent-continue?orderId=123&decision=true" -H 'Content-Type: application/json'
+curl -X POST "localhost:8080/wfp/externalevent-continue?orderId=123&decision=true" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -346,13 +341,12 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"wordCount":60}'
-background: true
timeout_seconds: 90
-->
```sh
-sleep 45 && curl -X POST localhost:8080/wfp/fanoutin -H 'Content-Type: application/json' -d @body.json
+curl -X POST localhost:8080/wfp/fanoutin -H 'Content-Type: application/json' -d @body.json --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -398,13 +392,12 @@ To start the workflow, you can run:
name: Start Suspend/Resume Workflow
match_order: none
output_match_mode: substring
-background: true
timeout_seconds: 90
-->
```sh
-sleep 50 && curl -X POST "localhost:8080/wfp/suspendresume?orderId=456" -H 'Content-Type: application/json'
+curl -X POST "localhost:8080/wfp/suspendresume?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -431,7 +424,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'SUSPENDED'
-background: true
timeout_seconds: 90
-->
@@ -439,7 +431,7 @@ timeout_seconds: 90
Let's suspend the workflow instance by sending the following request:
```sh
-sleep 55 && curl -X POST "localhost:8080/wfp/suspendresume/suspend?orderId=456" -H 'Content-Type: application/json'
+curl -X POST "localhost:8080/wfp/suspendresume/suspend?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -459,7 +451,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'RUNNING'
-background: true
timeout_seconds: 90
-->
@@ -467,7 +458,7 @@ timeout_seconds: 90
To send the event you can run:
```sh
-sleep 60 && curl -X POST "localhost:8080/wfp/suspendresume/resume?orderId=456" -H 'Content-Type: application/json'
+curl -X POST "localhost:8080/wfp/suspendresume/resume?orderId=456" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
@@ -487,7 +478,6 @@ match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"approved":true}'
-background: true
timeout_seconds: 90
-->
@@ -495,7 +485,7 @@ timeout_seconds: 90
To send the event you can run:
```sh
-sleep 65 && curl -X POST "localhost:8080/wfp/suspendresume/continue?orderId=456&decision=true" -H 'Content-Type: application/json'
+curl -X POST "localhost:8080/wfp/suspendresume/continue?orderId=456&decision=true" -H 'Content-Type: application/json' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90
```
diff --git a/spring-boot-examples/workflows/pom.xml b/spring-boot-examples/workflows/pom.xml
index 5a664c7f2f..e6d71acc11 100644
--- a/spring-boot-examples/workflows/pom.xml
+++ b/spring-boot-examples/workflows/pom.xml
@@ -6,7 +6,7 @@
io.dapr
spring-boot-examples
- 0.16.0-rc-1
+ 1.16.0-rc-1
workflows
diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java
index b27900652a..a9ca48bfbc 100644
--- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java
+++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java
@@ -15,6 +15,7 @@
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
+import io.dapr.testcontainers.DaprLogLevel;
import io.github.microcks.testcontainers.MicrocksContainersEnsemble;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -31,6 +32,16 @@
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
+/**
+ * Test configuration for Dapr containers with debug logging enabled.
+ *
+ * This configuration sets up Dapr with DEBUG log level and console output
+ * for detailed logging during test execution.
+ *
+ * ADDITIONAL DEBUGGING: For even more detailed logs, you can also:
+ * 1. Run `docker ps` to find the Dapr container ID
+ * 2. Run `docker logs --follow ` to stream real-time logs
+ */
@TestConfiguration(proxyBeanMethods = false)
public class DaprTestContainersConfig {
diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java
index 916aaa7bc6..80a9cda01a 100644
--- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java
+++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java
@@ -38,6 +38,23 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+/**
+ * Integration tests for Dapr Workflow Patterns.
+ *
+ * DEBUGGING: For more detailed logs during test execution, you can:
+ * 1. Run `docker ps` to find the Dapr container ID
+ * 2. Run `docker logs --follow ` to stream real-time logs
+ * 3. The container name will typically be something like "dapr-workflow-patterns-app-"
+ *
+ * Example:
+ * ```bash
+ * docker ps | grep dapr
+ * docker logs --follow
+ * ```
+ *
+ * This will show you detailed Dapr runtime logs including workflow execution,
+ * state transitions, and component interactions.
+ */
@SpringBootTest(classes = {TestWorkflowPatternsApplication.class, DaprTestContainersConfig.class,
DaprAutoConfiguration.class, },
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@@ -137,6 +154,12 @@ void testExternalEventDeny() {
}
+ /**
+ * Tests the ContinueAsNew workflow pattern.
+ *
+ * The ContinueAsNew pattern should execute cleanup activities 5 times
+ * with 5-second intervals between each iteration.
+ */
@Test
void testContinueAsNew() {
//This call blocks until all the clean up activities are executed
diff --git a/testcontainers-dapr/pom.xml b/testcontainers-dapr/pom.xml
index e35bbb1442..f55145b01f 100644
--- a/testcontainers-dapr/pom.xml
+++ b/testcontainers-dapr/pom.xml
@@ -11,7 +11,7 @@
testcontainers-dapr
testcontainers-dapr
Testcontainers Dapr Module
- 0.16.0-rc-1
+ 1.16.0-rc-1
jar
@@ -56,6 +56,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
+
+ true
+
attach-javadocs
diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java
index dd24e0a467..f326073366 100644
--- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java
+++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainerConstants.java
@@ -14,7 +14,7 @@
package io.dapr.testcontainers;
public interface DaprContainerConstants {
- String DAPR_VERSION = "1.16.0-rc.3";
+ String DAPR_VERSION = "1.16.0-rc.5";
String DAPR_RUNTIME_IMAGE_TAG = "daprio/daprd:" + DAPR_VERSION;
String DAPR_PLACEMENT_IMAGE_TAG = "daprio/placement:" + DAPR_VERSION;
String DAPR_SCHEDULER_IMAGE_TAG = "daprio/scheduler:" + DAPR_VERSION;