As @Slaw already suspected in the question comments while I composed this answer, its fundamentally a sequencing problem, hidden by a bit of compiler syntactic sugar.
JLS 14.20.3.2 states that this:
/* task creation if option 1 */
try (/* option 1 or 2 resource statement here */) {
var taskFuture = taskExecutor.submit(() -> {
task.run();
return true;
});
var result = taskFuture.get(30_000, TimeUnit.MILLISECONDS);
System.out.println("Got result: " + result);
} catch (Exception e) {
System.out.println("Caught exception: " + e);
throw new RuntimeException(e);
} /* finally if option 1 */
is fundamentally treated as this:
/* task creation if option 1 */
try{
try (/* option 1 or 2 resource statement here */){
var taskFuture = taskExecutor.submit(() -> {
task.run();
return true;
});
var result = taskFuture.get(30_000, TimeUnit.MILLISECONDS);
System.out.println("Got result: " + result);
}
} catch (Exception e) {
System.out.println("Caught exception: " + e);
throw new RuntimeException(e);
} /* finally if option 1 */
Which already hints at the problem.
taskExecutor.close() will happen (as part of exiting the inner try) before your own finally ever executes, leading to taskExecutor shutdown hanging forever* because the AtomicBoolean value is not yet true.AtomicBoolean to true) before the call to taskExecutor.close(), which means that method returns as soon as the task has (quite soon, if not already) terminated.