I'm getting this exception:
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:?]
This exception is rare and not reproducible in my system.
Looking at CompletableFuture.java it seems the f Function variable is null.
But there is a null check for it in the second line of uniApply(), so f cannot be null.
So why the JVM claims I have a NPE on the function invocation line? If the NPE is coming from within the invoked function, shouldn't I see it in the stack trace?
The relevant part from 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;
}
Relevant code from 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;
}
Here is how I invoke ConcurrentUtils.buildCompletableFuture:
ConcurrentUtils.buildCompletableFuture(asyncTask,
            ObjectWithTimestamp::getObject, s -> {},
            e -> logger.error("error message"), null, MoreExecutors.directExecutor())
The f in uniApply() is actually ObjectWithTimestamp::getObject
