Edit

Quickstart: Create a Java Durable Functions app

Use Durable Functions, a feature of Azure Functions, to write stateful serverless workflows in Java. In this quickstart, you clone and run a sample app that demonstrates two common orchestration patterns:

  • Function chaining: Calls activities sequentially (Tokyo → Seattle → London).
  • Fan-out/fan-in: Calls activities in parallel across five cities, then aggregates the results.

By the end, you'll have both orchestrations running locally with the Durable Task Scheduler emulator and be able to view their status in the dashboard.

  • Clone and prepare the Hello Cities sample project.
  • Set up the Durable Task Scheduler emulator and Azurite for local development.
  • Build and run the function app and trigger both orchestrations.
  • Review orchestration status and output in the Durable Task Scheduler dashboard.

Prerequisites

Set up the Durable Task Scheduler emulator

The Durable Task Scheduler emulator provides a local development environment so you can test orchestrations without an Azure subscription. The Java Functions host also requires Azurite for local storage.

Start both containers:

docker run -d --name dtsemulator -p 8080:8080 -p 8082:8082 \
  mcr.microsoft.com/dts/dts-emulator:latest

docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 \
  mcr.microsoft.com/azure-storage/azurite

Tip

Once the emulator is running, you can access the Durable Task Scheduler dashboard at http://localhost:8082 to monitor orchestrations.

Run the quickstart sample

  1. Navigate to the Hello Cities sample directory:

    cd samples/durable-functions/java/HelloCities
    
  2. Verify that the local.settings.json file contains the following configuration:

    {
      "IsEncrypted": false,
      "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "java",
        "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
      }
    }
    
  3. Build the project:

    mvn clean package
    
  4. Start the function app:

    mvn azure-functions:run
    
  5. In a separate terminal, trigger the function chaining orchestration:

    $response = Invoke-RestMethod -Method POST -Uri http://localhost:7071/api/StartChaining
    $response
    

    The response contains status URLs for the orchestration instance. Copy the statusQueryGetUri value and run it to check the result:

    Invoke-RestMethod -Uri $response.statusQueryGetUri
    
  6. Trigger the fan-out/fan-in orchestration:

    $response = Invoke-RestMethod -Method POST -Uri http://localhost:7071/api/StartFanOutFanIn
    Invoke-RestMethod -Uri $response.statusQueryGetUri
    

Expected output

The POST request returns a JSON response with status URLs. For example:

{
  "id": "<instanceId>",
  "statusQueryGetUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/<instanceId>?code=...",
  "sendEventPostUri": "...",
  "terminatePostUri": "...",
  "purgeHistoryDeleteUri": "..."
}

When you query statusQueryGetUri and the orchestration's runtimeStatus is Completed, you can find the greeting results in the output field. The chaining orchestration returns:

{
  "name": "ChainingOrchestration",
  "runtimeStatus": "Completed",
  "output": "Hello Tokyo! Hello Seattle! Hello London!"
}

The fan-out/fan-in orchestration returns:

{
  "name": "FanOutFanInOrchestration",
  "runtimeStatus": "Completed",
  "output": ["Hello Tokyo!", "Hello Seattle!", "Hello London!", "Hello Paris!", "Hello Berlin!"]
}

Tip

If runtimeStatus shows Running or Pending, wait a moment and query the statusQueryGetUri again.

Open the Durable Task Scheduler dashboard at http://localhost:8082 to view the orchestration status and execution history.

Understand the code

The sample project in src/main/java/com/example/Functions.java contains all three function types needed for a Durable Functions app.

Activity function

The SayHello activity takes a city name and returns a greeting:

@FunctionName("SayHello")
public String sayHello(
        @DurableActivityTrigger(name = "city") String city) {
    return "Hello " + city + "!";
}

Orchestrator functions

The chaining orchestrator calls SayHello sequentially for three cities:

@FunctionName("ChainingOrchestration")
public String chainingOrchestration(
        @DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {

    String result = "";
    result += ctx.callActivity("SayHello", "Tokyo", String.class).await();
    result += " " + ctx.callActivity("SayHello", "Seattle", String.class).await();
    result += " " + ctx.callActivity("SayHello", "London", String.class).await();
    return result;
}

The fan-out/fan-in orchestrator schedules activities in parallel:

@FunctionName("FanOutFanInOrchestration")
public List<String> fanOutFanInOrchestration(
        @DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {

    String[] cities = {"Tokyo", "Seattle", "London", "Paris", "Berlin"};
    List<Task<String>> parallelTasks = new ArrayList<>();

    for (String city : cities) {
        parallelTasks.add(ctx.callActivity("SayHello", city, String.class));
    }

    List<String> results = new ArrayList<>();
    for (Task<String> task : parallelTasks) {
        results.add(task.await());
    }

    return results;
}

Client functions

HTTP-triggered client functions start each orchestration:

@FunctionName("StartChaining")
public HttpResponseMessage startChaining(
        @HttpTrigger(name = "req", methods = {HttpMethod.POST},
            authLevel = AuthorizationLevel.ANONYMOUS)
            HttpRequestMessage<Void> request,
        @DurableClientInput(name = "durableContext") DurableClientContext durableContext) {

    DurableTaskClient client = durableContext.getClient();
    String instanceId = client.scheduleNewOrchestrationInstance("ChainingOrchestration");
    return durableContext.createCheckStatusResponse(request, instanceId);
}

Configuration

The sample uses the Durable Task Scheduler emulator as its storage backend. This is configured in host.json:

{
  "extensions": {
    "durableTask": {
      "hubName": "default",
      "storageProvider": {
        "type": "azureManaged",
        "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
      }
    }
  }
}

The emulator connection string is set in local.settings.json:

{
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "java",
    "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
  }
}

Clean up resources

Stop the emulator containers when you're done:

docker stop dtsemulator azurite && docker rm dtsemulator azurite

Next steps