Durable Task Scheduler — Java
Learn More
Durable Functions Setup
Required Maven Dependencies
<dependencies>
<dependency>
<groupId>com.microsoft.azure.functions</groupId>
<artifactId>azure-functions-java-library</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>com.microsoft</groupId>
<artifactId>durabletask-azure-functions</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>đź’ˇ Finding latest versions: Search Maven Central for
durabletask-azure-functions(group:com.microsoft) to find the current stable version.
host.json
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "durabletask-scheduler",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}local.settings.json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "java",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
}
}Minimal Example
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;
import com.microsoft.durabletask.*;
import com.microsoft.durabletask.azurefunctions.*;
public class DurableFunctionsApp {
@FunctionName("HttpStart")
public HttpResponseMessage httpStart(
@HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION)
HttpRequestMessage<Void> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext) {
DurableTaskClient client = durableContext.getClient();
String instanceId = client.scheduleNewOrchestrationInstance("MyOrchestration");
return durableContext.createCheckStatusResponse(request, instanceId);
}
@FunctionName("MyOrchestration")
public String myOrchestration(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String result1 = ctx.callActivity("SayHello", "Tokyo", String.class).await();
String result2 = ctx.callActivity("SayHello", "Seattle", String.class).await();
return result1 + ", " + result2;
}
@FunctionName("SayHello")
public String sayHello(@DurableActivityTrigger(name = "name") String name) {
return "Hello " + name + "!";
}
}Workflow Patterns
Fan-Out/Fan-In
@FunctionName("FanOutFanIn")
public List<String> fanOutFanIn(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String[] cities = {"Tokyo", "Seattle", "London", "Paris", "Berlin"};
List<Task<String>> parallelTasks = new ArrayList<>();
// Fan-out: schedule all activities in parallel
for (String city : cities) {
parallelTasks.add(ctx.callActivity("SayHello", city, String.class));
}
// Fan-in: wait for all to complete
List<String> results = new ArrayList<>();
for (Task<String> task : parallelTasks) {
results.add(task.await());
}
return results;
}Human Interaction
@FunctionName("ApprovalWorkflow")
public String approvalWorkflow(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
ctx.callActivity("SendApprovalRequest", ctx.getInput(String.class)).await();
// Wait for approval event with timeout
Task<Boolean> approvalTask = ctx.waitForExternalEvent("ApprovalEvent", Boolean.class);
Task<Void> timeoutTask = ctx.createTimer(Duration.ofDays(3));
Task<?> winner = ctx.anyOf(approvalTask, timeoutTask).await();
if (winner == approvalTask) {
return approvalTask.await() ? "Approved" : "Rejected";
}
return "Timed out";
}Orchestration Determinism
| ❌ NEVER | ✅ ALWAYS USE |
|---|---|
System.currentTimeMillis() | ctx.getCurrentInstant() |
UUID.randomUUID() | Pass random values from activities |
Thread.sleep() | ctx.createTimer() |
| Direct I/O, HTTP, database | ctx.callActivity() |
Replay-Safe Logging
private static final java.util.logging.Logger logger =
java.util.logging.Logger.getLogger("MyOrchestration");
@FunctionName("MyOrchestration")
public String myOrchestration(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
// Use isReplaying to avoid duplicate logs
if (!ctx.getIsReplaying()) {
logger.info("Started"); // Only logs once, not on replay
}
return ctx.callActivity("MyActivity", "input", String.class).await();
}Error Handling & Retry
@FunctionName("WorkflowWithRetry")
public String workflowWithRetry(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
TaskOptions retryOptions = new TaskOptions(new RetryPolicy(
3, // maxNumberOfAttempts
Duration.ofSeconds(5) // firstRetryInterval
));
try {
return ctx.callActivity("UnreliableService", ctx.getInput(String.class),
retryOptions, String.class).await();
} catch (TaskFailedException ex) {
ctx.setCustomStatus(Map.of("Error", ex.getMessage()));
ctx.callActivity("CompensationActivity", ctx.getInput(String.class)).await();
return "Compensated";
}
}Durable Task SDK (Non-Functions)
For applications running outside Azure Functions (containers, VMs, Azure Container Apps, Azure Kubernetes Service):
import com.microsoft.durabletask.*;
import com.microsoft.durabletask.azuremanaged.DurableTaskSchedulerWorkerExtensions;
import com.microsoft.durabletask.azuremanaged.DurableTaskSchedulerClientExtensions;
import java.time.Duration;
public class App {
public static void main(String[] args) throws Exception {
String connectionString = "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// Worker
DurableTaskGrpcWorker worker = DurableTaskSchedulerWorkerExtensions
.createWorkerBuilder(connectionString)
.addOrchestration(new TaskOrchestrationFactory() {
@Override public String getName() { return "MyOrchestration"; }
@Override public TaskOrchestration create() {
return ctx -> {
String result = ctx.callActivity("SayHello",
ctx.getInput(String.class), String.class).await();
ctx.complete(result);
};
}
})
.addActivity(new TaskActivityFactory() {
@Override public String getName() { return "SayHello"; }
@Override public TaskActivity create() {
return ctx -> "Hello " + ctx.getInput(String.class) + "!";
}
})
.build();
worker.start();
// Client
DurableTaskClient client = DurableTaskSchedulerClientExtensions
.createClientBuilder(connectionString).build();
String instanceId = client.scheduleNewOrchestrationInstance("MyOrchestration", "World");
OrchestrationMetadata result = client.waitForInstanceCompletion(
instanceId, Duration.ofSeconds(30), true);
System.out.println("Output: " + result.readOutputAs(String.class));
worker.stop();
}
}