From 255cb6f73b5d57f6602b9463dfbf15bc85295a55 Mon Sep 17 00:00:00 2001 From: torosent <17064840+torosent@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:41:04 -0800 Subject: [PATCH 1/3] Add descriptive error when orchestration type is not registered Replace NullPointerException with IllegalStateException containing a descriptive message when a worker receives an orchestration event for a type it doesn't have registered. This helps users understand that workers that don't support the requested orchestration type are connected to the task hub. --- .../microsoft/durabletask/TaskOrchestrationExecutor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java b/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java index 8fdb3f7a..ce155a67 100644 --- a/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java +++ b/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java @@ -882,7 +882,13 @@ private void processEvent(HistoryEvent e) { // Try getting the default orchestrator factory = TaskOrchestrationExecutor.this.orchestrationFactories.get("*"); } - // TODO: Throw if the factory is null (orchestration by that name doesn't exist) + if (factory == null) { + throw new IllegalStateException( + "No orchestration factory registered for orchestration type '" + name + "'. " + + "This usually means that a worker that doesn't support this orchestration type " + + "is connected to this task hub. Make sure the worker has a registered " + + "orchestration for '" + name + "'."); + } TaskOrchestration orchestrator = factory.create(); orchestrator.run(this); break; From 3ab0040948d7a1e29423c54987bee0471ce5bc7f Mon Sep 17 00:00:00 2001 From: torosent <17064840+torosent@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:43:29 -0800 Subject: [PATCH 2/3] Add unit test for unregistered orchestration type error message Verifies that executing an orchestration with an unregistered type produces a FAILED result with an IllegalStateException containing the orchestration name and guidance about mismatched workers. --- .../TaskOrchestrationExecutorTest.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 client/src/test/java/com/microsoft/durabletask/TaskOrchestrationExecutorTest.java diff --git a/client/src/test/java/com/microsoft/durabletask/TaskOrchestrationExecutorTest.java b/client/src/test/java/com/microsoft/durabletask/TaskOrchestrationExecutorTest.java new file mode 100644 index 00000000..7d62df2b --- /dev/null +++ b/client/src/test/java/com/microsoft/durabletask/TaskOrchestrationExecutorTest.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.durabletask; + +import com.google.protobuf.StringValue; +import com.google.protobuf.Timestamp; +import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.*; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.*; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for TaskOrchestrationExecutor. + */ +public class TaskOrchestrationExecutorTest { + + private static final Logger logger = Logger.getLogger(TaskOrchestrationExecutorTest.class.getName()); + + @Test + void execute_unregisteredOrchestrationType_failsWithDescriptiveMessage() { + // Arrange: create executor with no registered orchestrations + HashMap factories = new HashMap<>(); + TaskOrchestrationExecutor executor = new TaskOrchestrationExecutor( + factories, + new JacksonDataConverter(), + Duration.ofDays(3), + logger, + null); + + String unknownName = "NonExistentOrchestration"; + + // Build history events simulating an orchestration start + HistoryEvent orchestratorStarted = HistoryEvent.newBuilder() + .setEventId(-1) + .setTimestamp(Timestamp.getDefaultInstance()) + .setOrchestratorStarted(OrchestratorStartedEvent.getDefaultInstance()) + .build(); + + HistoryEvent executionStarted = HistoryEvent.newBuilder() + .setEventId(-1) + .setTimestamp(Timestamp.getDefaultInstance()) + .setExecutionStarted(ExecutionStartedEvent.newBuilder() + .setName(unknownName) + .setVersion(StringValue.of("")) + .setInput(StringValue.of("\"test-input\"")) + .setOrchestrationInstance(OrchestrationInstance.newBuilder() + .setInstanceId("test-instance-id") + .build()) + .build()) + .build(); + + HistoryEvent orchestratorCompleted = HistoryEvent.newBuilder() + .setEventId(-1) + .setTimestamp(Timestamp.getDefaultInstance()) + .setOrchestratorCompleted(OrchestratorCompletedEvent.getDefaultInstance()) + .build(); + + List pastEvents = Arrays.asList(orchestratorStarted, executionStarted, orchestratorCompleted); + List newEvents = Collections.emptyList(); + + // Act + TaskOrchestratorResult result = executor.execute(pastEvents, newEvents); + + // Assert: the result should contain a CompleteOrchestrationAction with FAILED status + // and a failure message mentioning the unknown orchestration name + OrchestratorAction action = result.getActions().iterator().next(); + assertTrue(action.hasCompleteOrchestration(), "Expected a CompleteOrchestrationAction"); + + CompleteOrchestrationAction completeAction = action.getCompleteOrchestration(); + assertEquals(OrchestrationStatus.ORCHESTRATION_STATUS_FAILED, completeAction.getOrchestrationStatus()); + assertTrue(completeAction.hasFailureDetails(), "Expected failure details"); + + TaskFailureDetails failureDetails = completeAction.getFailureDetails(); + assertEquals("java.lang.IllegalStateException", failureDetails.getErrorType()); + assertTrue(failureDetails.getErrorMessage().contains(unknownName), + "Error message should contain the orchestration name: " + failureDetails.getErrorMessage()); + assertTrue(failureDetails.getErrorMessage().contains("worker"), + "Error message should mention workers: " + failureDetails.getErrorMessage()); + } +} From 567d569219285ce9836a425af0a5cbcf611af217 Mon Sep 17 00:00:00 2001 From: Tomer Rosenthal <17064840+torosent@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:46:07 -0800 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../microsoft/durabletask/TaskOrchestrationExecutor.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java b/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java index ce155a67..9c70db02 100644 --- a/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java +++ b/client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java @@ -883,11 +883,10 @@ private void processEvent(HistoryEvent e) { factory = TaskOrchestrationExecutor.this.orchestrationFactories.get("*"); } if (factory == null) { - throw new IllegalStateException( - "No orchestration factory registered for orchestration type '" + name + "'. " + - "This usually means that a worker that doesn't support this orchestration type " + - "is connected to this task hub. Make sure the worker has a registered " + - "orchestration for '" + name + "'."); + throw new IllegalStateException(String.format( + "No orchestration factory registered for orchestration type '%s'. This usually means that a worker that doesn't support this orchestration type is connected to this task hub. Make sure the worker has a registered orchestration for '%s'.", + name, + name)); } TaskOrchestration orchestrator = factory.create(); orchestrator.run(this);