Backend Development 14 min read

Eight Ways to Implement Asynchronous Execution in Java

This article introduces eight common methods for implementing asynchronous execution in Java, including threads, Future, CompletableFuture, Spring @Async, ApplicationEvent, message queues, Hutool ThreadUtil, and Guava ListenableFuture, and provides code examples and usage considerations.

Architect's Guide
Architect's Guide
Architect's Guide
Eight Ways to Implement Asynchronous Execution in Java

Asynchronous execution is familiar to developers and can greatly reduce request latency in many scenarios such as sending SMS, email, or updating data asynchronously.

Eight Ways to Implement Asynchrony

Thread

Future

CompletableFuture

Spring @Async annotation

Spring ApplicationEvent

Message queue

Third‑party async frameworks (e.g., Hutool ThreadUtil)

Guava async

What Is Asynchrony?

Consider a typical order‑placement flow where sending an SMS and awarding points are independent operations. In a synchronous implementation the thread must wait for the point‑granting logic to finish before sending the SMS, which blocks the request. By executing these two tasks asynchronously they can run in parallel, shortening the overall response time.

1. Thread Asynchrony

public class AsyncThread extends Thread {
    @Override
    public void run() {
        System.out.println("Current thread name:" + Thread.currentThread().getName() + " Send email success!");
    }
    public static void main(String[] args) {
        AsyncThread asyncThread = new AsyncThread();
        asyncThread.run();
    }
}

Creating a new Thread for each task is costly; a thread pool is preferred:

private ExecutorService executorService = Executors.newCachedThreadPool();

public void fun() {
    executorService.submit(new Runnable() {
        @Override
        public void run() {
            log.info("Execute business logic...");
        }
    });
}

The business logic can be wrapped in a Runnable or Callable and submitted to the pool.

2. Future Asynchrony

@Slf4j
public class FutureManager {
    public String execute() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        Future
future = executor.submit(new Callable
() {
            @Override
            public String call() throws Exception {
                System.out.println("--- task start ---");
                Thread.sleep(3000);
                System.out.println("--- task finish ---");
                return "this is future execute final result!!!";
            }
        });
        // get() blocks until the result is ready
        String result = future.get();
        log.info("Future get result: {}", result);
        return result;
    }
    @SneakyThrows
    public static void main(String[] args) {
        FutureManager manager = new FutureManager();
        manager.execute();
    }
}

Output:

--- task start ---
--- task finish ---
Future get result: this is future execute final result!!!

Shortcomings of Future

Cannot receive task completion notifications passively; the caller must call get() to obtain the result.

Each Future is isolated; chaining multiple asynchronous steps requires manual coordination.

Error handling is limited; exceptions are only visible when get() throws.

3. CompletableFuture Asynchrony

public class CompletableFutureCompose {
    @SneakyThrows
    public static void thenRunAsync() {
        CompletableFuture
cf1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
        CompletableFuture
cf2 = cf1.thenRunAsync(() -> {
            System.out.println(Thread.currentThread() + " cf2 do something...");
        });
        System.out.println("cf1 result ->" + cf1.get());
        System.out.println("cf2 result ->" + cf2.get());
    }
    public static void main(String[] args) {
        thenRunAsync();
    }
}

CompletableFuture uses an internal ForkJoinPool and can be configured with a custom executor if needed.

4. Spring @Async Asynchrony

Define a custom thread pool:

@EnableAsync
@Configuration
public class TaskPoolConfig {
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        int cores = Runtime.getRuntime().availableProcessors();
        System.out.println("System max threads: " + cores);
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(16);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(99999);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("asyncServiceExecutor-");
        executor.setAwaitTerminationSeconds(60);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

Service interface and implementation using @Async("taskExecutor") :

public interface AsyncService {
    MessageResult sendSms(String callPrefix, String mobile, String actionType, String content);
    MessageResult sendEmail(String email, String subject, String content);
}

@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
    @Autowired
    private IMessageHandler messageHandler;

    @Override
    @Async("taskExecutor")
    public MessageResult sendSms(String callPrefix, String mobile, String actionType, String content) {
        try {
            Thread.sleep(1000);
            messageHandler.sendSms(callPrefix, mobile, actionType, content);
        } catch (Exception e) {
            log.error("Send SMS exception ->", e);
        }
        return null;
    }

    @Override
    @Async("taskExecutor")
    public MessageResult sendEmail(String email, String subject, String content) {
        try {
            Thread.sleep(1000);
            messageHandler.sendEmail(email, subject, content);
        } catch (Exception e) {
            log.error("Send email exception ->", e);
        }
        return null;
    }
}

It is recommended to use a custom thread pool rather than the default @Async executor.

5. Spring ApplicationEvent Asynchrony

Define an event class:

public class AsyncSendEmailEvent extends ApplicationEvent {
    private String email;
    private String subject;
    private String content;
    private String targetUserId;
    // getters and setters omitted
}

Event listener handling the event asynchronously:

@Slf4j
@Component
public class AsyncSendEmailEventHandler implements ApplicationListener
{
    @Autowired
    private IMessageHandler messageHandler;

    @Async("taskExecutor")
    @Override
    public void onApplicationEvent(AsyncSendEmailEvent event) {
        if (event == null) return;
        messageHandler.sendEmail(event.getEmail(), event.getSubject(), event.getContent());
    }
}

When combined with Spring Retry, this approach can provide compensation for failures.

6. Message Queue Asynchrony

Producer sending a delayed callback message:

@Slf4j
@Component
public class CallbackProducer {
    @Autowired
    AmqpTemplate amqpTemplate;

    public void sendCallbackMessage(CallbackDTO callbackDTO, final long delayTimes) {
        log.info("Producer sends callbackDTO: {}", callbackDTO);
        amqpTemplate.convertAndSend(
            CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getExchange(),
            CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getRoutingKey(),
            JsonMapper.getInstance().toJson(callbackDTO),
            new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    message.getMessageProperties().setHeader("x-delay", delayTimes);
                    message.getMessageProperties().setCorrelationId(callbackDTO.getSdkId());
                    return message;
                }
            }
        );
    }
}

Consumer processing the callback:

@Slf4j
@Component
@RabbitListener(queues = "message.callback", containerFactory = "rabbitListenerContainerFactory")
public class CallbackConsumer {
    @Autowired
    private IGlobalUserService globalUserService;

    @RabbitHandler
    public void handle(String json, Channel channel, @Headers Map
map) throws Exception {
        if (map.get("error") != null) {
            channel.basicNack((Long) map.get(AmqpHeaders.DELIVERY_TAG), false, true);
            return;
        }
        try {
            CallbackDTO callbackDTO = JsonMapper.getInstance().fromJson(json, CallbackDTO.class);
            globalUserService.execute(callbackDTO);
            channel.basicAck((Long) map.get(AmqpHeaders.DELIVERY_TAG), false);
        } catch (Exception e) {
            log.error("Callback failure -> {}", e);
        }
    }
}

7. Hutool ThreadUtil Asynchrony

@Slf4j
public class ThreadUtils {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            ThreadUtil.execAsync(() -> {
                int number = ThreadLocalRandom.current().nextInt(20) + 1;
                System.out.println(number);
            });
            log.info("Current thread index: " + i);
        }
        log.info("task finish!");
    }
}

8. Guava ListenableFuture Asynchrony

Guava’s ListenableFuture extends the JDK Future and adds an addListener method, allowing callbacks without blocking.

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
final ListenableFuture
listenableFuture = executorService.submit(new Callable
() {
    @Override
    public Integer call() throws Exception {
        log.info("callable execute...");
        TimeUnit.SECONDS.sleep(1);
        return 1;
    }
});

Futures.addCallback(listenableFuture, new FutureCallback
() {
    @Override
    public void onSuccess(Integer result) {
        System.out.println("Get listenable future's result with callback " + result);
    }
    @Override
    public void onFailure(Throwable t) {
        t.printStackTrace();
    }
});

The article concludes by encouraging readers to discuss the eight asynchronous techniques.

JavaSpringasynchronousCompletableFuturethreadFuture
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.