8 Powerful Ways to Implement Asynchronous Execution in Java

Understanding asynchronous execution in Java can dramatically reduce latency for tasks such as sending SMS, emails, or updating data, and this article walks through eight practical implementations—from raw Threads and Futures to Spring @Async, ApplicationEvent, message queues, ThreadUtil, and Guava ListenableFuture—complete with code samples and best‑practice tips.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
8 Powerful Ways to Implement Asynchronous Execution in Java

Asynchronous execution is common in development to reduce request latency, for example sending SMS, emails, or performing async updates.

Eight ways to implement async

Thread

Future

CompletableFuture

Spring @Async

Spring ApplicationEvent

Message Queue

Third‑party frameworks such as Hutool ThreadUtil

Guava async

What is async?

Consider an order‑placement scenario where sending an SMS and awarding points are independent. In synchronous code the second step must wait for the first, while asynchronous execution allows both to run concurrently.

order flow diagram
order flow diagram

Using async, the two operations can be performed simultaneously.

async illustration
async illustration

1. Thread async

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 each time wastes resources; a thread pool is preferred.

private ExecutorService executorService = Executors.newCachedThreadPool();

public void fun() {
    executorService.submit(new Runnable() {
        @Override
        public void run() {
            log.info("执行业务逻辑...");
        }
    });
}

2. Future async

@Slf4j
public class FutureManager {
    public String execute() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        Future<String> future = executor.submit(new Callable<String>() {
            @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 if result is needed
        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 passively receive task result; must call get() explicitly.

Futures are isolated; no built‑in pipeline support.

Lacks robust error handling; exceptions must be caught from get().

3. CompletableFuture async

public class CompletableFutureCompose {
    @SneakyThrows
    public static void thenRunAsync() {
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
        CompletableFuture<Void> cf2 = cf1.thenRunAsync(() -> {
            System.out.println(Thread.currentThread() + " cf2 do something...");
        });
        System.out.println("cf1结果->" + cf1.get());
        System.out.println("cf2结果->" + cf2.get());
    }

    public static void main(String[] args) {
        thenRunAsync();
    }
}

CompletableFuture uses ForkJoinPool internally; a custom pool can be supplied when needed.

4. Spring @Async

Define a custom thread pool:

@EnableAsync
@Configuration
public class TaskPoolConfig {
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println("系统最大线程数  : " + i);
        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 implementation using @Async:

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

    @Override
    @Async("taskExecutor")
    public MessageResult sendSms(String callPrefix, String mobile, String actionType, String content) {
        try {
            Thread.sleep(1000);
            mesageHandler.sendSms(callPrefix, mobile, actionType, content);
        } catch (Exception e) {
            log.error("发送短信异常 -> ", e);
        }
    }

    @Override
    @Async("taskExecutor")
    public MessageResult sendEmail(String email, String subject, String content) {
        try {
            Thread.sleep(1000);
            mesageHandler.sendsendEmail(email, subject, content);
        } catch (Exception e) {
            log.error("发送email异常 -> ", e);
        }
    }
}

Custom @Async is preferred over the default thread pool.

5. Spring ApplicationEvent async

Define an event class and a listener that processes it asynchronously.

public class AsyncSendEmailEvent extends ApplicationEvent {
    private String email;
    private String subject;
    private String content;
    private String targetUserId;
    // getters and setters
}
@Component
public class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {
    @Autowired
    private IMessageHandler mesageHandler;

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

Combine with Spring Retry for error compensation when needed.

6. Message Queue

Producer example using RabbitTemplate with delayed messages:

@Component
public class CallbackProducer {
    @Autowired
    AmqpTemplate amqpTemplate;

    public void sendCallbackMessage(CallbackDTO callbackDTO, final long delayTimes) {
        log.info("生产者发送消息,callbackDTO,{}", callbackDTO);
        amqpTemplate.convertAndSend(CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getExchange(),
                CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getRoutingKey(),
                JsonMapper.getInstance().toJson(callbackDTO),
                message -> {
                    message.getMessageProperties().setHeader("x-delay", delayTimes);
                    message.getMessageProperties().setCorrelationId(callbackDTO.getSdkId());
                    return message;
                });
    }
}

Consumer example with manual acknowledgment:

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

    @RabbitHandler
    public void handle(String json, Channel channel, @Headers Map<String, Object> 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("回调失败 -> {}", e);
        }
    }
}

7. ThreadUtil async utility

public class ThreadUtils {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            ThreadUtil.execAsync(() -> {
                ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
                int number = threadLocalRandom.nextInt(20) + 1;
                System.out.println(number);
            });
            log.info("当前第:" + i + "个线程");
        }
        log.info("task finish!");
    }
}

8. Guava async

Guava's ListenableFuture extends JDK Future and allows listeners without polling.

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
final ListenableFuture<Integer> listenableFuture = executorService.submit(() -> {
    log.info("callable execute...");
    TimeUnit.SECONDS.sleep(1);
    return 1;
});
Futures.addCallback(listenableFuture, new FutureCallback<Integer>() {
    @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();
    }
});
JavaconcurrencySpringAsynchronousCompletableFuturethreadguavaMessageQueue
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

0 followers
Reader feedback

How this landed with the community

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.