-
Notifications
You must be signed in to change notification settings - Fork 51
Description
We are analyzing the behavior of the Java SDK TryExecutor.handleException() implementation (https://github.com/serverlessworkflow/sdk-java/blob/main/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java#L162C3-L197C4). The DSL spec states that an error must propagate when no catch filter/predicate matches the raised error, but the SDK currently returns CompletableFuture.completedFuture(taskContext.rawOutput()) before the filter is evaluated.
Please clarify the intended design with these questions:
-
Unmatched filter flow
- In the code below,
completableis initialized to a completed (successful) future before any error filter orwhen/exceptWhencheck. If the filter or predicate later evaluates tofalse, the method simply returns that success future instead of propagating the error. Is that intentional (design choice) or a bug? The DSL says the error should propagate (workflow should FAULT) when there is no match.
CompletableFuture<WorkflowModel> completable = CompletableFuture.completedFuture(taskContext.rawOutput()); if (filterMatches && WorkflowUtils.whenExceptTest(...)) { // catch.do and/or retry } return completable; // always SUCCESS, even when filter does not match
**Example:** TLS workflow raises a timeout error while the catch block is configured for validation errors. The relevant YAML is: ```yaml do: - attemptTask: try: - failingTask: raise: error: type: https://example.com/errors/timeout status: 408 title: Request Timeout catch: errors: with: type: https://example.com/errors/validation do: - handleValidation: set: recovered: true ``` Despite the timeout, the SDK still returns `CompletableFuture.completedFuture(...)` before seeing the mismatch, so the try task completes instead of faulting. - In the code below,
-
whenandexceptWhenpredicates-
Since the predicates short-circuit the condition, a
falsewhenortrueexceptWhenalso causes theifblock to be skipped. Yet the method still returns the success future created above. Shouldn’t the error be re-thrown whenwhen/exceptWhenreject the error, per the spec?Example: The error block raises a
503while thewhenpredicate only accepts status 400:do: - attemptTask: try: - failingTask: raise: error: type: https://example.com/errors/transient status: 503 catch: as: caughtError when: ${ .status == 400 } do: - handleError: set: recovered: true
Because the SDK returns the pre-resolved success future, the error never propagates even though
whenevaluated to false.
-
-
catch.dofailure propagation-
The same logic is reused when
catch.docontains nested try/catch. If an inner error is not matched, the innerhandleException()call returns the already-completed success future, so the catch.do chain never faulted even though the inner error should escape and eventually fault the workflow. Was this swallowing behavior intended?Example: An outer catch.do contains an inner try that raises an error but its filter rejects it:
do: - outerTry: try: - outerFailingTask: raise: ...outer error... catch: as: outerError do: - innerTryInCatchDo: try: - innerFailingTask: raise: ...inner error... catch: errors: with: type: https://example.com/errors/wrong-type do: - innerRecovery: { ... }
The inner error type is
https://example.com/errors/inner, so the inner filter does not match. The SDK still returns the success future from the nestedhandleException(), meaning the outer try thinks catch.do succeeded rather than faulting.
-
-
Error detail comparison
-
Can you confirm whether this line is a typo? https://github.com/serverlessworkflow/sdk-java/blob/main/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java#L210
// current SDK code compareString(errorFilter.getDetails(), errorFilter.getDetails()) // expected comparison compareString(errorFilter.getDetails(), error.details())
-
Why this matters: in the current code the detail filter is compared to itself, so that check is always
truewhendetailsis present. This makescatch.errors.with.detailseffectively non-functional, because mismatched error details are never rejected. -
Example scenario:
do: - attemptTask: try: - failingTask: raise: error: type: https://example.com/errors/security status: 403 details: User not found in tenant catalog catch: errors: with: type: https://example.com/errors/security details: Enforcement Failure - invalid email do: - handleSecurity: set: recovered: true
-
Expected behavior (DSL): detail mismatch means catch filter does not match, so the error should propagate and the workflow should fault.
-
Current SDK behavior: catch is treated as matched and
catch.doruns, so the workflow completes successfully.
-
-
catch.asbinding-
The caught error object is never injected into the expression context for catch.do (no
taskContextor model mutation with theasvariable). Is that deliberate, or should the error be bound so${ .caughtError.detail }works for expressions?Example: A catch block binds the caught error to
caughtErrorand reads itsdetail:do: - attemptTask: try: - failingTask: raise: ... catch: as: caughtError do: - handleAnyError: set: recovered: true errorMessage: ${ .caughtError.detail }
The SDK leaves
${ .caughtError }asnull, so the expressions fail even though DSL semantics require the error object to be available.
-
We’d appreciate confirmation on whether these behaviors are intentional or if they should be treated as bugs so we can follow up accordingly.