Skip to content

Instantly share code, notes, and snippets.

@jocull
Last active March 28, 2025 21:48
Show Gist options
  • Save jocull/37b1338cac89c6c01c3f937bb5aeb36e to your computer and use it in GitHub Desktop.
Save jocull/37b1338cac89c6c01c3f937bb5aeb36e to your computer and use it in GitHub Desktop.
Java CompletableFutures - causing a situation that will hang applications
package com.codefromjames;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import java.util.stream.IntStream;
public class Main {
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
private static final Random RANDOM = new Random();
record Result(String string, Duration duration) {
}
public static void main(String[] args) {
final List<CompletableFuture<Result>> futures1 = IntStream.range(0, 1000)
.mapToObj(i -> {
final CompletableFuture<Result> f = new CompletableFuture<>();
final Instant start = Instant.now();
getDelayedHelloFuture()
.thenApply(s -> {
final Instant end = Instant.now();
final Duration duration = Duration.between(start, end);
return new Result(s, duration);
})
// TODO: The problem is here.
// The future we're manually managing won't be completed due to a failing step
// and `thenAccept` or `thenCompose` methods will be skipped due to not being
// explicitly designed to handle exceptions.
//
// We can use `whenComplete` or `handle` instead to properly control our
// manually managed future.
.thenCompose(result -> {
f.complete(result);
return f;
});
// .whenComplete((result, throwable) -> {
// if (throwable != null) {
// f.completeExceptionally(throwable);
// } else {
// f.complete(result);
// }
// });
return f;
})
.toList();
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures1.toArray(new CompletableFuture[0]));
CompletableFuture<Object> handleStack = allOf.thenAccept(unused -> {
LOGGER.info("I accept that this is complete.");
}).whenComplete((unused, throwable) -> {
LOGGER.info("It's done.", throwable);
}).handle((unused, throwable) -> {
LOGGER.info("MANY WHELPS. HANDLE IT.", throwable);
return null;
});
LOGGER.info("_WELL_? We're _waiting_!");
handleStack.join();
}
private static CompletableFuture<String> getDelayedHelloFuture() {
final Executor delayedExecutor = CompletableFuture.delayedExecutor(RANDOM.nextLong(50, 5000), TimeUnit.MILLISECONDS);
return CompletableFuture.supplyAsync(() -> {
if (RANDOM.nextDouble() < 0.0001) { // A low chance of failing. But with many operations... anything can happen.
throw new RuntimeException("Bad luck exception");
}
return "Hello from " + Thread.currentThread().getName() + "!";
}, delayedExecutor)
.whenComplete((s, throwable) -> {
LOGGER.info("Just telling you about {} / {}", s, throwable != null);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment