Skip to content

CallbackFailedException throws NullPointerException when a failed callback has no Error #773

Closed

Description

@yaythomas

Description

SendDurableExecutionCallbackFailure accepts a request with no Error because the field is optional. A callback that is failed this way reaches the SDK as a CALLBACK operation with Status: FAILED and no CallbackDetails.Error.

On that operation, CallbackOperation.get() throws new CallbackFailedException(op):

The constructor passes operation.callbackDetails().error() to buildMessage. That method calls error.errorType() without a null check:

private static String buildMessage(ErrorObject error) {
var errorType = error.errorType();

So the handler gets a NullPointerException instead of CallbackFailedException, and a catch (CallbackFailedException e) block does not catch it. ChildContextOperation builds the same exception, so the same failure can occur there:

Steps to reproduce

  1. Create a callback in a durable handler and call get() on it.
  2. Call SendDurableExecutionCallbackFailure for that callback ID with no Error in the request body.
  3. On replay, get() throws NullPointerException from CallbackFailedException.buildMessage.

Expected behavior

get() throws CallbackFailedException with the message "Callback failed". This matches the reference JS implementation, which falls back to new CallbackExternalError("Callback failed") when Error is absent:

https://github.com/aws/aws-durable-execution-sdk-js/blob/0ee6c027ca3b58f3bf270fdf4a9ac87b73752467/packages/aws-durable-execution-sdk-js/src/handlers/callback-handler/callback.ts#L248-L273

Suggested fix

Handle a null ErrorObject in buildMessage:

private static String buildMessage(ErrorObject error) {
    if (error == null) {
        return "Callback failed";
    }
    var errorType = error.errorType();
    var errorMessage = error.errorMessage() != null ? error.errorMessage() : "Callback failed";
    if (errorType != null && !errorType.isEmpty()) {
        return errorType + ": " + errorMessage;
    }
    return errorMessage;
}

The errorMessage fallback also covers an Error that is present but has no message. JS uses error.ErrorMessage || "Callback failed" for that case.

DurableOperationException already accepts a null ErrorObject, so no other change is needed. Add a unit test for a FAILED callback operation with no CallbackDetails.Error.

Activity

  1. added
    bugSomething isn't working
    and removed on Oct 5, 2026
  2. added theissue type on Oct 5, 2026
  3. added a commit that references this issue on Oct 5, 2026
    9ab9f52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpkg:sdkModule: sdk

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions