multithreading – List Consumption asynchronously with Java CompletableFutures and Spring

Looking for reviews and possible enhancements of a utility I'm currently working on for a personal application with multiple sequential applications, Calls independent of the order and long. After finishing, I have a friend who is interested in the utility. To this end, [My Question] is "With focuses on ease of use, interview, and use cases not taken into account in the current designWhat improvements could be made here? "

The Utility contains a list (representing the parameters of the asynchronous tasks) and a consumer (representing the async task itself). The consumer is run for each item in the list, using asynchronous and multithreaded execution to improve performance.

Assumptions / environment:
The BootApp application in which this module resides is annotated with @EnableAsync and configures an executor bean named "threadPoolTaskExecutor", which defines the available thread pool for AsyncUtil.

The API of the util is defined in its interface class:


import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

public interface AsyncUtil {

/ **
* Exposes the configuration of the executor used to run Async
* @param exe
* /
void setTaskExecutor (Executor exe);

/ **
* Exposes the configuration of the executor used to run Async
* This method corresponds to the configuration "without operating system", specifying a number of threads for the executor.
* @param threadCount
* /
void configureExecutor (int threadCount);

/ **
* Uses the locally configured executor to execute the supplied consumer on each E element of the list.
     * @param list
* consumer @param
* @param 
     * @return
* /
     listing consumeListAsync (List list, consumer consumer);

/ **
* As above, but allows to provide an executor without the need for preconfiguration. Useful for cases with a single asynchronous call
* @param list
* consumer @param
* executor
* @param 
     * @return
* /
     listing consumeListAsync (List list, consumer consumer, executor);

For the sake of brevity, there is a large block of comments in the foregoing, as well as examples of use cases.


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;

The AsyncUtilImpl public class implements AsyncUtil {

private executor;

@Qualifier ("threadPoolTaskExecutor")
public void setTaskExecutor (Executor exe) {
this.executor = exe;

public void configureExecutor (int threadCount) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor ();
executor.setCorePoolSize (threadCount);
executor.setMaxPoolSize (threadCount);
executor.setThreadNamePrefix ("async_thread_");
executor.initialize ();
this.executor = executor;

public  listing consumeListAsync (List list, consumer consumer){
return consumeListAsync (list, consumer, this.excutor);

public  listing consumeListAsync (List list, consumer consumer, executor)
listing errors = new ArrayList <> ();
if (executor == null || executor.equals (null)) {start the new NullPointerException ("Executor null, make sure your executor is non-null or else the local executor is configured")}
List <CompleteFuture> asyncTaskList = list
.map (e -> AsyncCall
.accept (consumer, executing)
.handle ((info, error) -> {
if (error! = null) {
errors.add (error);
return null;
return CompleteFuture.completedFuture (info);
.thenCompose (Function.identity ())
) .collect (Collectors.toList ());

CompletableFuture process =
CompletableFuture.allOf (asyncTaskList.toArray (new CompletableFuture[asyncTaskList.size()]))
.thenRun (() -> (). map (future -> future.join ()));

process.get ();

} catch (Exception e) {
errors.add (e);

return errors;

private static class AsyncCall {
public static  CompletableFuture accept (Consumer consumer, E e, Executor executor) {
return CompletableFuture.runAsync (() -> consumer.accept (e), executor);


Incomplete explanation of the design / choices

First of all, the type of return:

The asynchronous call hides the exceptions thrown in the thread of execution. I handle these exceptions, create a list of these thread-level exceptions, and return them to the user. The user can choose how to respond to these errors and can use List.isEmpty () as an indicator of success.

Second, the zero returns:

I focused on a vacuum execution model because of the nature of my app's calls (mainly db CRUD, heavy C and U). These calls do not need a type of return and this is reflected in my design. however, the return of the data is simple when it is combined with an accumulator:

listing taskItems = ...
listing resultAccumulator = ...
AsncUtil.consumeListAsync (
(SomeType element) -> resultAccumulator.add (someReturningProcess (item))
doSomethingWithResults (resultAccumulator);

I will update this section with additional information based on comments / requests for more