### Question:
StageSupport { public static <T, U> CompletionStage<U> thenComposeOrRecover( final CompletionStage<T> stage, final BiFunction<? super T, Throwable, ? extends CompletionStage<U>> fn) { final CompletableFuture<U> ret = new CompletableFuture<>(); stage.whenComplete((t, throwable) -> { try { fn.apply(t, throwable).whenComplete((u, ex2) -> { if (ex2 != null) { ret.completeExceptionally(ex2); } ret.complete(u); }); } catch (final Throwable e) { ret.completeExceptionally(e); } }); return ret; } private  StageSupport(); static CompletionStage<Void> voidStage(); static CompletionStage<Void> voided(final CompletionStage<T> stage); static CompletionStage<T> completedStage(final T t); static CompletionStage<T> exceptionalStage(final Throwable ex); static CompletionStage<T> tryWith(
      final CompletionStage<R> resource,
      final Function<? super R, ? extends T> fn); static CompletionStage<T> tryComposeWith(
      final CompletionStage<R> resource,
      final Function<? super R, ? extends CompletionStage<T>> fn); static CompletionStage<T> tryWith(
      final R resource, final Function<? super R, ? extends T> fn); static CompletionStage<T> tryComposeWith(
      final R resource,
      final Function<? super R, ? extends CompletionStage<T>> fn); static CompletionStage<U> thenComposeOrRecover(
      final CompletionStage<T> stage,
      final BiFunction<? super T, Throwable, ? extends CompletionStage<U>> fn);  }### Answer:
@Test public void testThenComposeOrRecover() { final CompletionStage<Integer> error = StageSupport.exceptionalStage(new TestException()); final CompletionStage<Integer> success = StageSupport.completedStage(1); final CompletionStage<Integer> success2 = StageSupport.completedStage(2); for (final boolean inputFailed : new boolean[] {false, true}) { final CompletionStage<Integer> inputStage = inputFailed ? error : success; final Integer expectedResult = inputFailed ? null : 1; { final int x = StageSupport.thenComposeOrRecover( inputStage, (result, throwable) -> { Assert.assertEquals(expectedResult, result); Assert.assertEquals(inputFailed, throwable != null); return success2; }) .toCompletableFuture() .join(); Assert.assertEquals(2, x); } { assertError( StageSupport.<Integer, Integer>thenComposeOrRecover( inputStage, (result, throwable) -> { Assert.assertEquals(expectedResult, result); Assert.assertEquals(inputFailed, throwable != null); throw new TestException(); })); } { assertError( StageSupport.thenComposeOrRecover( inputStage, (result, throwable) -> { Assert.assertEquals(expectedResult, result); Assert.assertEquals(inputFailed, throwable != null); return error; })); } } }