Skip to content

fix(java-client): guard against NPE in FutureGroup.waitAllComplete when future cause is null - #2410

Open
cauchy1988 wants to merge 1 commit into
apache:masterfrom
cauchy1988:fix/futuregroup-npe-2152
Open

fix(java-client): guard against NPE in FutureGroup.waitAllComplete when future cause is null#2410
cauchy1988 wants to merge 1 commit into
apache:masterfrom
cauchy1988:fix/futuregroup-npe-2152

Conversation

@cauchy1988

Copy link
Copy Markdown
Contributor

Background

Closes #2152. When using batch APIs (Batch.asyncCommitFutureGroup.waitAllComplete, a.k.a. commitWaitAllComplete), a NullPointerException can occur intermittently.

Root cause

FutureGroup.waitAllComplete builds the failure message by dereferencing fu.cause():

} else {
    results.add(Pair.of(
        new PException("async task #[" + i + "] await failed: " + fu.cause().getMessage()),
        null));
}

A netty Future.cause() returns null not only on success, but also while the future is still incomplete. If await times out before the future ever resolves, isSuccess() is false (so we enter the else branch) but cause() is still null — thus cause().getMessage() throws NPE, crashing the entire batch commit instead of reporting just the single failed task.

The sibling method waitAllCompleteOrOneFail is unaffected because it passes fu.cause() directly to the PException constructor, which tolerates null.

Fix

Guard the dereference and fall back to a placeholder message when cause() is null:

Throwable cause = fu.cause();
String causeMsg = cause != null ? cause.getMessage() : "unknown cause (future not completed)";
results.add(Pair.of(new PException("async task #[" + i + "] await failed: " + causeMsg), null));

This matches the fix suggested by the issue reporter.

Test

Added TestFutureGroup.testWaitAllCompleteHandlesNullCause, which uses Mockito to mock an incomplete Future (await→false, isSuccess→false, cause→null) to reproduce the scenario deterministically, without relying on timing.

Verified both directions:

  • With the fix: all 3 tests in TestFutureGroup pass.
  • Without the fix (temporarily reverted): the new test fails with the exact NPE reported in the issue:
    NullPointerException: Cannot invoke "Throwable.getMessage()" because the return value of "io.netty.util.concurrent.Future.cause()" is null
        at FutureGroup.waitAllComplete(FutureGroup.java:117)
    

…en future cause is null

`waitAllComplete` dereferenced `fu.cause()` to build the failure message,
but a netty Future reports `cause() == null` not only on success but also
while it is still incomplete (e.g. after `await` timed out without the
future ever resolving). In that window `isSuccess()` is false yet
`cause()` is null, so `cause().getMessage()` threw a NullPointerException,
crashing the whole batch commit instead of reporting the single failed task.

Guard the dereference and fall back to a placeholder message when cause is
null. Add a regression test that mocks an incomplete Future to reproduce
the NPE deterministically (verified: reverting the fix makes the new test
fail with the exact NPE at FutureGroup.java:117, the location reported in
the issue).

Closes apache#2152
@cauchy1988
cauchy1988 force-pushed the fix/futuregroup-npe-2152 branch from 29a7cbf to 17765d2 Compare June 18, 2026 02:48
@github-actions github-actions Bot removed the cpp label Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug(java client): When using batch, a null pointer exception may occur

1 participant