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