Skip to content
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
Verify Compilation:
```shell
./gradlew compileJava
```

Build & test:

```shell
./gradlew build
```

## Updating Protobuf Definitions

When updating the protobuf definitions in `internal/durabletask-protobuf/protos/orchestrator_service.proto`:

1. Manually copy the updated protobuf file from dapr/durabletask-protobuf
2. Update the commit hash in `internal/durabletask-protobuf/PROTO_SOURCE_COMMIT_HASH` to reflect the new commit
3. Regenerate the Java classes from the protobuf definitions:

```shell
./gradlew generateProto
```
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Durable Task Client SDK for Java

[![Build](https://github.com/microsoft/durabletask-java/actions/workflows/build-validation.yml/badge.svg)](https://github.com/microsoft/durabletask-java/actions/workflows/build-validation.yml)
[![Build](https://github.com/dapr/durabletask-java/actions/workflows/build-validation.yml/badge.svg)](https://github.com/dapr/durabletask-java/actions/workflows/build-validation.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

This repo contains the Java SDK for the Durable Task Framework as well as classes and annotations to support running [Azure Durable Functions](https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-overview?tabs=java) for Java. With this SDK, you can define, schedule, and manage durable orchestrations using ordinary Java code.
Expand Down Expand Up @@ -78,8 +78,7 @@ The following packages are produced from this repo.

| Package | Latest version |
| - | - |
| Durable Task - Client | [![Maven Central](https://img.shields.io/maven-central/v/com.microsoft/durabletask-client?label=durabletask-client)](https://mvnrepository.com/artifact/com.microsoft/durabletask-client/1.0.0) |
| Durable Task - Azure Functions | [![Maven Central](https://img.shields.io/maven-central/v/com.microsoft/durabletask-azure-functions?label=durabletask-azure-functions)](https://mvnrepository.com/artifact/com.microsoft/durabletask-azure-functions/1.0.1) |
| Durable Task - Client | [![Maven Central](https://img.shields.io/maven-central/v/io.dapr/durabletask-client?label=durabletask-client)](https://mvnrepository.com/artifact/io.dapr/durabletask-client/1.5.7) |

## Getting started with Azure Functions

Expand Down
41 changes: 39 additions & 2 deletions client/src/main/java/io/dapr/durabletask/TaskOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,51 @@
public final class TaskOptions {
private final RetryPolicy retryPolicy;
private final RetryHandler retryHandler;
private final String appID;

public TaskOptions(RetryPolicy retryPolicy, RetryHandler retryHandler, String appID) {
this.retryPolicy = retryPolicy;
this.retryHandler = retryHandler;
this.appID = appID;
}

public TaskOptions(RetryPolicy retryPolicy, RetryHandler retryHandler) {
this.retryPolicy = retryPolicy;
this.retryHandler = retryHandler;
this.appID = null;
}

/**
* Creates a new {@code TaskOptions} object from a {@link RetryPolicy}.
* @param retryPolicy the retry policy to use in the new {@code TaskOptions} object.
*/
public TaskOptions(RetryPolicy retryPolicy) {
this(retryPolicy, null);
this(retryPolicy, null, null);
}

/**
* Creates a new {@code TaskOptions} object from a {@link RetryHandler}.
* @param retryHandler the retry handler to use in the new {@code TaskOptions} object.
*/
public TaskOptions(RetryHandler retryHandler) {
this(null, retryHandler);
this(null, retryHandler, null);
}

/**
* Creates a new {@code TaskOptions} object with the specified app ID.
* @param appID the app ID to use for cross-app workflow routing
*/
public TaskOptions(String appID) {
this(null, null, appID);
}

/**
* Creates a new {@code TaskOptions} object with the specified retry policy and app ID.
* @param retryPolicy the retry policy to use
* @param appID the app ID to use for cross-app workflow routing
*/
public TaskOptions(RetryPolicy retryPolicy, String appID) {
this(retryPolicy, null, appID);
}

boolean hasRetryPolicy() {
Expand All @@ -53,4 +78,16 @@ boolean hasRetryHandler() {
public RetryHandler getRetryHandler() {
return this.retryHandler;
}

/**
* Gets the configured app ID value or {@code null} if none was configured.
* @return the configured app ID
*/
public String getAppID() {
return this.appID;
}

boolean hasAppID() {
return this.appID != null && !this.appID.isEmpty();
}
}
88 changes: 88 additions & 0 deletions client/src/test/java/io/dapr/durabletask/TaskOptionsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/

package io.dapr.durabletask;

import org.junit.jupiter.api.Test;
import java.time.Duration;

import static org.junit.jupiter.api.Assertions.*;

/**
* Unit tests for TaskOptions with cross-app workflow support.
*/
public class TaskOptionsTest {

@Test
void taskOptionsWithAppID() {
TaskOptions options = new TaskOptions("app1");

assertTrue(options.hasAppID());
assertEquals("app1", options.getAppID());
assertFalse(options.hasRetryPolicy());
assertFalse(options.hasRetryHandler());
}

@Test
void taskOptionsWithRetryPolicyAndAppID() {
RetryPolicy retryPolicy = new RetryPolicy(3, Duration.ofSeconds(1));
TaskOptions options = new TaskOptions(retryPolicy, null, "app2");

assertTrue(options.hasAppID());
assertEquals("app2", options.getAppID());
assertTrue(options.hasRetryPolicy());
assertEquals(retryPolicy, options.getRetryPolicy());
assertFalse(options.hasRetryHandler());
}

@Test
void taskOptionsWithRetryHandlerAndAppID() {
RetryHandler retryHandler = new RetryHandler() {
@Override
public boolean handle(RetryContext context) {
return context.getLastAttemptNumber() < 2;
}
};
TaskOptions options = new TaskOptions(null, retryHandler, "app3");

assertTrue(options.hasAppID());
assertEquals("app3", options.getAppID());
assertFalse(options.hasRetryPolicy());
assertTrue(options.hasRetryHandler());
assertEquals(retryHandler, options.getRetryHandler());
}

@Test
void taskOptionsWithoutAppID() {
TaskOptions options = new TaskOptions(null, null, null);

assertFalse(options.hasAppID());
assertNull(options.getAppID());
}

@Test
void taskOptionsWithEmptyAppID() {
TaskOptions options = new TaskOptions("");

assertFalse(options.hasAppID());
assertEquals("", options.getAppID());
}

@Test
void taskOptionsWithNullAppID() {
TaskOptions options = new TaskOptions(null, null, null);

assertFalse(options.hasAppID());
assertNull(options.getAppID());
}
}
2 changes: 1 addition & 1 deletion internal/durabletask-protobuf/PROTO_SOURCE_COMMIT_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
cc00765eeb3307f8fdac7da610915d3a0757702b
ce184193bcf2d4dcf643bcc934d3ea897c4c8c5c
2 changes: 1 addition & 1 deletion internal/durabletask-protobuf/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Durable Task Protobuf Files

This directory contains the protocol buffer definitions used by the Durable Task Framework Java SDK. The files in this directory are automatically downloaded and updated during the build process from the [microsoft/durabletask-protobuf](https://github.com/microsoft/durabletask-protobuf) repository.
This directory contains the protocol buffer definitions used by the Durable Task Framework Java SDK. The files in this directory are automatically downloaded and updated during the build process from the [dapr/durabletask-protobuf](https://github.com/dapr/durabletask-protobuf) repository.

## Directory Structure

Expand Down
24 changes: 17 additions & 7 deletions internal/durabletask-protobuf/protos/orchestrator_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

syntax = "proto3";

option csharp_namespace = "Microsoft.DurableTask.Protobuf";
option csharp_namespace = "Dapr.DurableTask.Protobuf";
option java_package = "io.dapr.durabletask.implementation.protobuf";
option go_package = "/api/protos";

Expand All @@ -12,6 +12,11 @@ import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/empty.proto";

message TaskRouter {
string source = 1; // orchestrationAppID
string target = 2; // appID
}

message OrchestrationInstance {
string instanceId = 1;
google.protobuf.StringValue executionId = 2;
Expand Down Expand Up @@ -192,10 +197,10 @@ message EntityOperationCalledEvent {
}

message EntityLockRequestedEvent {
string criticalSectionId = 1;
string criticalSectionId = 1;
repeated string lockSet = 2;
int32 position = 3;
google.protobuf.StringValue parentInstanceId = 4; // used only within messages, null in histories
google.protobuf.StringValue parentInstanceId = 4; // used only within messages, null in histories
}

message EntityOperationCompletedEvent {
Expand All @@ -210,14 +215,14 @@ message EntityOperationFailedEvent {

message EntityUnlockSentEvent {
string criticalSectionId = 1;
google.protobuf.StringValue parentInstanceId = 2; // used only within messages, null in histories
google.protobuf.StringValue parentInstanceId = 2; // used only within messages, null in histories
google.protobuf.StringValue targetInstanceId = 3; // used only within histories, null in messages
}

message EntityLockGrantedEvent {
string criticalSectionId = 1;
}

message HistoryEvent {
int32 eventId = 1;
google.protobuf.Timestamp timestamp = 2;
Expand All @@ -244,25 +249,28 @@ message HistoryEvent {
ExecutionResumedEvent executionResumed = 22;
EntityOperationSignaledEvent entityOperationSignaled = 23;
EntityOperationCalledEvent entityOperationCalled = 24;
EntityOperationCompletedEvent entityOperationCompleted = 25;
EntityOperationFailedEvent entityOperationFailed = 26;
EntityOperationCompletedEvent entityOperationCompleted = 25;
EntityOperationFailedEvent entityOperationFailed = 26;
EntityLockRequestedEvent entityLockRequested = 27;
EntityLockGrantedEvent entityLockGranted = 28;
EntityUnlockSentEvent entityUnlockSent = 29;
}
optional TaskRouter router = 30;
}

message ScheduleTaskAction {
string name = 1;
google.protobuf.StringValue version = 2;
google.protobuf.StringValue input = 3;
optional TaskRouter router = 4;
}

message CreateSubOrchestrationAction {
string instanceId = 1;
string name = 2;
google.protobuf.StringValue version = 3;
google.protobuf.StringValue input = 4;
optional TaskRouter router = 5;
}

message CreateTimerAction {
Expand Down Expand Up @@ -311,6 +319,7 @@ message OrchestratorAction {
TerminateOrchestrationAction terminateOrchestration = 7;
SendEntityMessageAction sendEntityMessage = 8;
}
optional TaskRouter router = 9;
}

message OrchestratorRequest {
Expand All @@ -320,6 +329,7 @@ message OrchestratorRequest {
repeated HistoryEvent newEvents = 4;
OrchestratorEntityParameters entityParameters = 5;
bool requiresHistoryStreaming = 6;
optional TaskRouter router = 7;
}

message OrchestratorResponse {
Expand Down