You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CompletableFuture调用uniApply时出现NullPointerException的问题排查

偶发NullPointerException排查疑惑

我遇到了如下异常:

Caused by: java.lang.NullPointerException
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:616) ~[?:1.8.0_302]
at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:591) ~[?:1.8.0_302]
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488) ~[?:1.8.0_302]
at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1975) ~[?:1.8.0_302]
at com.tcom.concurrent.ConcurrentUtils$3.onSuccess(ConcurrentUtils.java:140) ~[framework-20220815.38-RELEASE.jar:?]

该异常属于偶发现象,无法在系统中复现。

查看CompletableFuture.java源码后,初步怀疑是f这个Function变量为空导致的,但uniApply()方法第二行已经对f做了空检查,f不可能为空。

那为什么JVM会报告在函数调用行出现NPE?如果NPE是在被调用函数内部抛出的,栈跟踪应该会体现出来才对,这是怎么回事?


ConcurrentUtils.java相关代码:

public static <I, O> CompletableFuture<O> buildCompletableFuture(final ListenableFuture<I> listenableFuture,
                                                                 final Function<I, O> responseApplier,
                                                                 final Consumer<I> onSuccessConsumer,
                                                                 final Consumer<Throwable> onFailureConsumer,
                                                                 final Supplier<O> defaultValueOnExceptionSupplier,
                                                                 final Executor callBackExecutor) {
    //create an instance of CompletableFuture
    final CompletableFuture<I> innerComplete = new CompletableFuture<I>() {
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            // propagate cancel to the listenable future
            boolean result = listenableFuture.cancel(mayInterruptIfRunning);
            super.cancel(mayInterruptIfRunning);
            return result;
        }
    };

    // add callback
    Futures.addCallback(listenableFuture, new FutureCallback<I>() {
        @Override
        public void onSuccess(I result) {
            innerComplete.complete(result);
            if (onSuccessConsumer != null) {
                onSuccessConsumer.accept(result);
            }
        }

        @Override
        public void onFailure(Throwable t) {
            innerComplete.completeExceptionally(t);
            if (onFailureConsumer != null) {
                onFailureConsumer.accept(t);
            }
        }
    }, callBackExecutor);

    CompletableFuture<O> returnedFuture = innerComplete.thenApply(responseApplier);
    if (defaultValueOnExceptionSupplier != null) { //when we have default value the exception will not thrown
        returnedFuture = returnedFuture.exceptionally((ex) -> defaultValueOnExceptionSupplier.get());
    }

    return returnedFuture;
}

CompletableFuture相关代码:

final <S> boolean uniApply(CompletableFuture<S> a,
                           Function<? super S,? extends T> f,
                           UniApply<S,T> c) {
    Object r; Throwable x;
    if (a == null || (r = a.result) == null || f == null)
        return false;
    tryComplete: if (result == null) {
        if (r instanceof AltResult) {
            if ((x = ((AltResult)r).ex) != null) {
                completeThrowable(x, r);
                break tryComplete;
            }
            r = null;
        }
        try {
            if (c != null && !c.claim())
                return false;
            @SuppressWarnings("unchecked") S s = (S) r;
            completeValue(f.apply(s)); // This is CompletableFuture.java:616
        } catch (Throwable ex) {
            completeThrowable(ex);
        }
    }
    return true;
}

调用ConcurrentUtils.buildCompletableFuture的方式:

ConcurrentUtils.buildCompletableFuture(asyncTask,
            ObjectWithTimestamp::getObject, s -> {},
            e -> logger.error("error message"), null, MoreExecutors.directExecutor())

uniApply()中的f实际就是ObjectWithTimestamp::getObject


问题分析与解决方向:

  1. NPE的真实来源:
    栈跟踪指向f.apply(s),但f已通过空检查,真实原因是**s为null时调用实例方法引用ObjectWithTimestamp::getObject抛出NPE**。JDK 8部分版本中,Lambda/方法引用抛出异常时,栈跟踪会被优化,导致异常的实际触发点没有完整打印,只显示到调用Lambda的位置。

  2. 偶发原因:
    异常偶发说明asyncTask返回的结果偶尔为null,或返回了AltResult类型且内部异常为null的对象——后者会在uniApply逻辑中被转为null,最终导致s为null。

  3. 解决方案:

    • 提前校验输入:将方法引用改为显式Lambda,提前检查参数是否为null:
      s -> {
          if (s == null) {
              // 可返回默认值或抛出明确异常
              return null;
          }
          return s.getObject();
      }
      
    • 拦截null结果:在ConcurrentUtils的onSuccess方法中添加null检查,避免将null传入innerComplete:
      @Override
      public void onSuccess(I result) {
          if (result == null) {
              innerComplete.completeExceptionally(new NullPointerException("ListenableFuture返回结果为null"));
          } else {
              innerComplete.complete(result);
          }
          if (onSuccessConsumer != null) {
              onSuccessConsumer.accept(result);
          }
      }
      
    • 排查上游任务:检查asyncTask为何会偶尔返回null或异常结果,从根源避免无效数据流入后续逻辑。

内容的提问来源于stack exchange,提问作者Lior Halfon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 12:36:18