Repository navigation
[Bug]: Type-only callback errors append ': null' to caller and telemetry messages #849
Description
Expected Behavior
A callback failed with only ErrorType: "RejectedError" must not produce the literal text "null" as if it were an error message. The caller-facing exception should have a useful type-only display message, and telemetry records should not publish RejectedError: null.
Preserve the original ErrorObject, including the absence of ErrorMessage, and retain the existing callback exception class and retry/replay behavior.
Actual Behavior
CallbackFailedException.buildMessage concatenates errorType + ": " + errorMessage whenever the type is present, including when the message is null. Consequently, a type-only external failure produces:
RejectedError: null
The core-generated message also reaches Durable Execution Telemetry. Its nameAndMessage helper reads the checkpointed ErrorObject when available, but falls back to Throwable.getMessage() when ErrorMessage is absent. An uncaught CallbackFailedException therefore produces a telemetry error whose name can correctly be RejectedError while its message is still RejectedError: null. A containing operation that checkpoints that caller error can carry the malformed message into later snapshots as well.
This affects error records sent to the configured telemetry destinations; it is not limited to OTel exception events. The callback leaf's original error type can already be preserved by the telemetry exporter, so do not conflate this message-formatting defect with #847.
Steps to Reproduce
The core formatting behavior has this minimal reproduction using public types:
var operation = Operation.builder()
.id("callback")
.type(OperationType.CALLBACK)
.status(OperationStatus.FAILED)
.callbackDetails(CallbackDetails.builder()
.error(ErrorObject.builder()
.errorType("RejectedError")
.build())
.build())
.build();
var failure = new CallbackFailedException(operation);
System.out.println(failure.getMessage()); // RejectedError: nullFor the full SDK-to-telemetry path:
- Register
DurableExecutionTelemetry.durableExecutionTelemetry(...)with a capturing exporter and error details enabled. - Run a named wait-for-callback and complete its callback with failure carrying only ErrorType.
- Allow the callback failure to escape the handler. Inspect the top-level exported
error.message, and any containing operation error that was checkpointed. - Also test a caught failure followed by a durable wait and resume, checking the stored operation error and exported record without repeating completed user work.
SDK Version
Source inspected at Java commit 4b1aa82b0d898fea495cda45760ce3737fbb341e. Exact deployed core/exporter JAR versions for the supplied observation were not recorded.
Java Version
Not recorded for the supplied cloud observation. The source defect is ordinary null String concatenation.
Is this a regression?
Unknown; no last-known-good version was established.
Source Evidence
- CallbackFailedException.buildMessage
- Telemetry name/message conversion and fallback
- Execution error included in telemetry records
Acceptance Criteria
- Type-only callback failures do not append
: null; message-only and type-plus-message failures preserve their supplied text. - Cover absent Error, empty ErrorObject, absent/empty type, and absent/empty message without introducing a new exception or changing failure classification.
- Preserve the underlying stored ErrorObject and its absent fields; define the caller's type-only display message explicitly.
- Add focused coverage through the real SDK and telemetry plugin for uncaught and caught/resumed failures. Check root and applicable operation error records, including the name-keyed format when a single operation represents that name.
- Preserve replayed results, callback identity and side-effect counts.
- Do not broaden this fix into the separate OTel operation exception-type issue [Bug]: OTel operation spans report DurableOperationException instead of the original error type #847.
Source inspection confirms the formatting and telemetry fallback paths. No new cloud deployment or raw backend retrieval was performed for filing this issue. Related #773 fixed the absent-Error NullPointerException; it does not cover the type-only : null message.
Activity
hln33 commentedon Oct 30, 2026
Severity - low
What is wrong.
CallbackFailedException.getMessage()returnsRejectedError: nullwhen an external system fails a callback withErrorTypeand noErrorMessage. The telemetry exporter falls back to that string forerror.message, and the OTel plugins put it in the exception entries on the Workflow and Invocation spans. A child context that checkpoints the escaped exception stores the malformed text as the operation'sErrorMessage.What is correct. The error type is preserved everywhere:
error.namein telemetry records isRejectedError, and the exception class is stillCallbackFailedException. Failure classification, retries, replay, callback identity, and the storedErrorObjectfor the callback itself are unchanged. Failures that carry anErrorMessage, or carry neither field, format correctly.Scope. Only callbacks failed externally with a type-only error payload. Both fields are optional in
SendDurableExecutionCallbackFailure, but most senders include a message.Not a regression.
buildMessagehas had this shape since the callback implementation landed on 2026-01-28, before v2.1.0. Every release since carries it. #773 fixed the adjacent absent-ErrorNPE and did not cover this case.Why not higher severity. No data loss, no outage, no change to execution behavior. The type is intact, so a customer can still identify the failure. The only persisted damage is a message string in a containing operation's checkpoint.
Why not lower severity. The text
nullreaches customers through a publicgetMessage()contract and through two exporters, and it can be written into execution history. The fix is a null check in one method, so the cost of leaving it is higher than the cost of fixing it. The acceptance criteria are already written; this is a good first issue.