@@ -222,14 +222,16 @@ void closeCompletesAllQueuedWaitersWithCancellation() {
222222
223223 sem .close ();
224224
225- // CompletableFuture# join unwraps CancellationException specifically: it surfaces directly
226- // rather than being wrapped in CompletionException. That's the same propagation downstream
227- // observers see, so we assert the bare exception shape here .
225+ // join() surfaces a CancellationException, but its shape is JDK-dependent: JDK 17 rethrows the
226+ // original directly (message "AsyncSemaphore is closed"), while JDK 21+ wraps it in a fresh
227+ // CancellationException (message "join") carrying the original as its cause. Accept either .
228228 for (CompletableFuture <Void > w : List .of (w1 , w2 , w3 )) {
229229 assertThat (w ).isCompletedExceptionally ();
230230 assertThatThrownBy (w ::join )
231231 .isInstanceOf (CancellationException .class )
232- .hasMessageContaining ("closed" );
232+ .satisfiesAnyOf (
233+ t -> assertThat (t ).hasMessageContaining ("closed" ),
234+ t -> assertThat (t ).hasRootCauseMessage ("AsyncSemaphore is closed" ));
233235 }
234236 assertThat (sem .queueLength ()).isZero ();
235237 }
@@ -244,7 +246,9 @@ void acquireAfterCloseReturnsFailedFutureImmediately() {
244246 assertThat (failed ).isCompletedExceptionally ();
245247 assertThatThrownBy (failed ::join )
246248 .isInstanceOf (CancellationException .class )
247- .hasMessageContaining ("closed" );
249+ .satisfiesAnyOf (
250+ t -> assertThat (t ).hasMessageContaining ("closed" ),
251+ t -> assertThat (t ).hasRootCauseMessage ("AsyncSemaphore is closed" ));
248252 assertThat (sem .queueLength ()).isZero ();
249253 }
250254
0 commit comments