Functionally, the two approaches are more or less the same:
- you submit your tasks for execution;
- you don’t wait for the result.
Technically, however, there are some subtle differences:
- In the second approach, you didn’t specify an executor, so it will use the common
ForkJoinPool. You would have to pass an executor as second argument ofsupplyAsync()if you don’t want that; - The
CompletableFutureAPI allows to easily chain more calls withthenApply(),thenCompose()etc. It is thus more flexible than the simpleFuturereturned byExecutorService.submit(); - Using
CompletableFutureallows to easily return a future from yourchild()method usingreturn CompletableFuture.allOf(the previously created futures).
Concerning readability, it’s a matter of preference, but if you want equivalent code the CompletableFuture approach might be considered a bit less readable once you have formatted it similarly. Compare:
executorService.submit(MyFileService::service1);
executorService.submit(MyFileService::service2);
executorService.submit(MyFileService::service3);
with
CompletableFuture.supplyAsync(MyFileService::service1, executorService);
CompletableFuture.supplyAsync(MyFileService::service2, executorService);
CompletableFuture.supplyAsync(MyFileService::service3, executorService);