This sample demonstrates Work Item Filtering, a feature that allows workers to declare which orchestrations, activities, and entities they can process. The Durable Task Scheduler (DTS) backend routes work items only to workers whose filters match, preventing workers from receiving work they cannot handle.
Before work item filtering, all orchestrations, activities, and entities were handed to any connected worker regardless of what it actually hosted. This caused errors (or silent hangs) when a worker received a work item it didn't implement — especially problematic in multi-service deployments, rolling upgrades, and microservice topologies. With filtering, each worker registers its task set; DTS creates per-filter queues and routes work items to matching workers. If no filter is specified, behavior falls back to the "generic queue" (all workers receive everything).
┌─────────────────────────────────────────────────────────────┐
│ Durable Task Scheduler (DTS) │
│ │
│ Orchestration queue ──► routed to Orchestrator Worker only │
│ ValidateOrder queue ──► routed to Validator Worker only │
│ ShipOrder queue ──► routed to Shipper Worker only │
└────────────┬──────────────────┬──────────────────┬──────────┘
│ │ │
┌───────▼───────┐ ┌──────▼───────┐ ┌──────▼───────┐
│ Orchestrator │ │ Validator │ │ Shipper │
│ Worker │ │ Worker │ │ Worker │
│ │ │ │ │ │
│ Registers: │ │ Registers: │ │ Registers: │
│ • OrderProc- │ │ • Validate- │ │ • ShipOrder │
│ essing- │ │ Order │ │ │
│ Orchestration│ │ │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
┌───────────────┐
│ Client │
│ (Driver) │
│ │
│ Schedules new │
│ orchestrations │
│ and prints │
│ results │
└───────────────┘
Orchestrator Worker runs orchestrations only — it has no activities registered.
Validator Worker runs ValidateOrder only — it has no orchestrations or other activities.
Shipper Worker runs ShipOrder only — same isolation.
Client schedules orchestrations and polls for completion.
OrderProcessingOrchestration performs two sequential activity calls:
ValidateOrder(orderId)→ routed to Validator WorkerShipOrder(orderId)→ routed to Shipper Worker
Returns a combined result string.
docker pull mcr.microsoft.com/dts/dts-emulator:latest
docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latestThe emulator dashboard is available at http://localhost:8082.
cd samples/scenarios/WorkItemFilteringSplitActivitiesJava
./gradlew buildTerminal 1 — Orchestrator Worker:
./gradlew :orchestrator-worker:runTerminal 2 — Validator Worker (ValidateOrder activity):
./gradlew :validator-worker:runTerminal 3 — Shipper Worker (ShipOrder activity):
./gradlew :shipper-worker:run./gradlew :client:runThe client runs in a continuous loop, scheduling a batch of 3 orchestrations every 30 seconds for 10 minutes. This makes it easy to observe scaling behavior over time.
10:30:01 INFO i.d.samples.Client - === Work Item Filtering Demo — Client ===
10:30:01 INFO i.d.samples.Client - Will schedule 3 orchestrations every 30s for 10 minutes.
10:30:01 INFO i.d.samples.Client - --- Batch #1 at 2025-01-01T10:30:01Z ---
10:30:01 INFO i.d.samples.Client - Scheduling orchestration with orderId='ORD-B001-001'...
10:30:01 INFO i.d.samples.Client - -> Scheduled with InstanceId=abc123
10:30:01 INFO i.d.samples.Client - Scheduling orchestration with orderId='ORD-B001-002'...
10:30:01 INFO i.d.samples.Client - -> Scheduled with InstanceId=def456
10:30:01 INFO i.d.samples.Client - Scheduling orchestration with orderId='ORD-B001-003'...
10:30:01 INFO i.d.samples.Client - -> Scheduled with InstanceId=ghi789
10:30:02 INFO i.d.samples.Client - COMPLETED | InstanceId=abc123 | Output: Order 'ORD-B001-001' => Validation: [Order ORD-B001-001 is valid], Shipping: [Shipped with tracking TRACK-ORD-B001-001-4271]
...
10:30:02 INFO i.d.samples.Client - Batch #1 results: 3 completed, 0 failed
10:30:02 INFO i.d.samples.OrchestratorWorker - [Orchestrator] Orchestration | Name=OrderProcessingOrchestration | InstanceId=abc123 | Processing order 'ORD-B001-001'
10:30:02 INFO i.d.samples.OrchestratorWorker - [Orchestrator] Orchestration | InstanceId=abc123 | Dispatching ValidateOrder to Validator Worker...
10:30:02 INFO i.d.samples.OrchestratorWorker - [Orchestrator] Orchestration | InstanceId=abc123 | Dispatching ShipOrder to Shipper Worker...
10:30:02 INFO i.d.samples.OrchestratorWorker - [Orchestrator] Orchestration | InstanceId=abc123 | Completed: Order 'ORD-B001-001' => Validation: [...], Shipping: [...]
10:30:02 INFO i.d.samples.ValidatorWorker - [Validator] Activity | Name=ValidateOrder | Validating order 'ORD-B001-001'...
10:30:02 INFO i.d.samples.ValidatorWorker - [Validator] Activity | Name=ValidateOrder | Result: Order ORD-B001-001 is valid
10:30:02 INFO i.d.samples.ShipperWorker - [Shipper] Activity | Name=ShipOrder | Shipping order 'ORD-B001-001'...
10:30:02 INFO i.d.samples.ShipperWorker - [Shipper] Activity | Name=ShipOrder | Result: Shipped with tracking TRACK-ORD-B001-001-4271
Key observation: Each worker processes only its registered work item types. No cross-processing occurs.
- Stop Shipper Worker (Ctrl+C in Terminal 3).
- Run the Client again to schedule new orchestrations.
- Observe that:
- Orchestrator Worker picks up and starts orchestrations.
- Validator Worker completes
ValidateOrderfor each order. ShipOrderwork items remain pending — they are not delivered to Validator Worker or Orchestrator Worker.- The orchestrations stay in "Running" status, waiting for the
ShipOrderactivity to complete.
- Restart Shipper Worker — the pending
ShipOrderwork items are immediately delivered and the orchestrations complete.
This demonstrates that filtering is strict: work items are routed only to workers with matching filters. There is no fallback to other workers.
Each worker process builds a DurableTaskGrpcWorker with only its own orchestrations or activities registered, then calls useWorkItemFilters() to auto-generate filters from the registry:
DurableTaskGrpcWorker worker = DurableTaskSchedulerWorkerExtensions
.createWorkerBuilder(connectionString)
.addActivity(new TaskActivityFactory() { /* ... */ })
.useWorkItemFilters() // auto-generate from registered tasks
.build();- Orchestrator Worker's filter:
orchestrations: [OrderProcessingOrchestration] - Validator Worker's filter:
activities: [ValidateOrder] - Shipper Worker's filter:
activities: [ShipOrder]
DTS creates per-filter queues and routes each work item to the matching queue. If a filter list is empty for a given type (e.g., Validator Worker has no orchestration filter), that worker simply never receives work items of that type.
To supply explicit filters instead of auto-generating them, use useWorkItemFilters(WorkItemFilter):
WorkItemFilter filter = WorkItemFilter.newBuilder()
.addOrchestration("OrderProcessingOrchestration")
.addActivity("ValidateOrder")
.build();
builder.useWorkItemFilters(filter);This sample includes full infrastructure-as-code (Bicep) and an azure.yaml for one-command deployment via Azure Developer CLI (azd).
| Resource | Purpose |
|---|---|
| Resource Group | Contains all resources |
| Durable Task Scheduler (Consumption SKU) | Managed orchestration backend |
| Task Hub | Logical unit for orchestrations and work items |
| Container Apps Environment | Shared hosting environment with VNet integration |
| Azure Container Registry | Stores Docker images for each service |
| User-Assigned Managed Identity | Shared identity with DTS Worker/Client RBAC role |
| 4 Container Apps | Client, Orchestrator Worker, Validator Worker, Shipper Worker |
cd samples/scenarios/WorkItemFilteringSplitActivitiesJava
azd upYou'll be prompted for an environment name, subscription, and location. The deployment takes ~5 minutes.
Each worker Container App is configured with a DTS-aware KEDA custom scale rule (azure-durabletask-scheduler) that scales based on the work item backlog in the task hub. The key parameter is workItemType, which tells the scaler what kind of work to monitor:
| Container App | Service Name | workItemType |
Scales on |
|---|---|---|---|
| Client | client |
Orchestration |
Pending orchestration work items |
| Orchestrator Worker | orchestrator-worker |
Orchestration |
Pending orchestration work items |
| Validator Worker | validator-worker |
Activity |
Pending activity work items |
| Shipper Worker | shipper-worker |
Activity |
Pending activity work items |
Workers scale from 0 to 10 replicas. When the client finishes its loop and no more work items arrive, workers scale back to zero.
Set the ENDPOINT and TASKHUB environment variables to point to your deployed scheduler:
export ENDPOINT="https://your-scheduler.westus2.durabletask.io"
export TASKHUB="your-taskhub-name"The workers and client will automatically use DefaultAzureCredential for authentication. Make sure the identity running each process has the Durable Task Scheduler Worker / Durable Task Scheduler Client role on the scheduler resource.
WorkItemFilteringSplitActivitiesJava/
├── build.gradle # Root Gradle build (shared dependencies)
├── settings.gradle # Multi-project definition
├── README.md
├── azure.yaml # azd service definitions
├── .gitignore
├── infra/ # Bicep infrastructure-as-code
│ ├── main.bicep # Top-level — resource group, DTS, container apps
│ ├── main.parameters.json
│ ├── abbreviations.json
│ ├── app/
│ │ ├── app.bicep # Per-service container app (with KEDA scale rule)
│ │ ├── dts.bicep # DTS scheduler + task hub
│ │ └── user-assigned-identity.bicep
│ └── core/
│ ├── host/ # Container Apps Environment, Registry, App template
│ ├── networking/ # VNet
│ └── security/ # ACR pull role, DTS role assignments
├── shared/ # Shared connection helper
│ ├── build.gradle
│ └── src/main/java/io/durabletask/samples/
│ └── ConnectionHelper.java
├── client/ # Schedules orchestrations in a loop, prints results
│ ├── build.gradle
│ ├── Dockerfile
│ └── src/main/java/io/durabletask/samples/
│ └── Client.java
├── orchestrator-worker/ # Orchestrator Worker — runs orchestrations only
│ ├── build.gradle
│ ├── Dockerfile
│ └── src/main/java/io/durabletask/samples/
│ └── OrchestratorWorker.java
├── validator-worker/ # Validator Worker — runs ValidateOrder activity only
│ ├── build.gradle
│ ├── Dockerfile
│ └── src/main/java/io/durabletask/samples/
│ └── ValidatorWorker.java
└── shipper-worker/ # Shipper Worker — runs ShipOrder activity only
├── build.gradle
├── Dockerfile
└── src/main/java/io/durabletask/samples/
└── ShipperWorker.java