Outside of the invisible side-effects pointed out by Antoniossss in the comments, which drives this a bit away from functional programming, the other issue is that you are still repeating yourself with all these identityComposing() wrapping calls.
As you said, the requirement is that those methods are called in sequence, so semantically you don’t really need the "composition" result. The intent is really to chain the calls themselves.
This would be best represented by composing those calls directly instead:
futureR.thenAccept(r -> sequentialFutureTask1(r)
.thenCompose(ignored -> sequentialFutureTask2(r))
.thenCompose(ignored -> sequentialFutureTask3(r)));
Of course, there is still a lot of repetition here with the thenCompose(ignored -> …(r)). If this is a frequent pattern you have, you could wrap this in the following method:
@SafeVarargs
public final <R> void executeInSequence(R r,
Function<? super R, ? extends CompletionStage<?>> firstTask,
Function<? super R, ? extends CompletionStage<?>>... otherTasks) {
CompletionStage<?> prev = firstTask.apply(r);
for (Function<? super R, ? extends CompletionStage<?>> task : otherTasks) {
prev = prev.thenCompose(ignored -> task.apply(r));
}
}
(I also wanted to do this with Stream but I didn’t find a nice, clean solution)
which you would use as:
futureR.thenAccept(r -> executeInSequence(r,
this::sequentialFutureTask1,
this::sequentialFutureTask2,
this::sequentialFutureTask3));
or if you prefer to pass futureR as parameter:
@SafeVarargs
public final <R> void executeAfter(CompletionStage<R> futureR,
Function<? super R, ? extends CompletionStage<?>> firstTask,
Function<? super R, ? extends CompletionStage<?>>... otherTasks) {
futureR.thenAccept(r -> {
CompletionStage<?> prev = firstTask.apply(r);
for (Function<? super R, ? extends CompletionStage<?>> task : otherTasks) {
prev = prev.thenCompose(ignored -> task.apply(r));
}
});
}
used as:
executeAfter(futureR,
this::sequentialFutureTask1,
this::sequentialFutureTask2,
this::sequentialFutureTask3);
Alternatively, this executeAfter() can be implemented with one less parameter:
@SafeVarargs
public final <R> void executeAfter2(CompletionStage<R> futureR,
Function<? super R, ? extends CompletionStage<?>>... otherTasks) {
futureR.thenAccept(r -> {
CompletionStage<?> prev = futureR;
for (Function<? super R, ? extends CompletionStage<?>> task : otherTasks) {
prev = prev.thenCompose(ignored -> task.apply(r));
}
});
}
but this prev = futureR causes an additional, useless thenCompose() that might be confusing (performance impact should be negligible since futureR is already completed when this will be invoked).