Master 8 Ways to Implement Asynchronous Programming in Java

This article explains why asynchronous execution is essential for tasks like sending SMS or emails, defines async concepts, and walks through eight practical Java implementations—including Thread, Future, CompletableFuture, Spring @Async, ApplicationEvent, message queues, ThreadUtil, and Guava ListenableFuture—complete with code examples and usage tips.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Master 8 Ways to Implement Asynchronous Programming in Java

1. Introduction

Asynchronous execution is familiar to developers and can dramatically reduce request‑chain latency for scenarios such as sending SMS, email, or updating data.

2. Eight Ways to Implement Asynchronous Execution

Thread

Future

CompletableFuture

Spring @Async annotation

Spring ApplicationEvent

Message queue

Third‑party frameworks like Hutool ThreadUtil

Guava asynchronous utilities

3. What Is Asynchronous?

In a synchronous flow, the "send SMS" step must finish before "grant points" can run, causing unnecessary waiting. Since these two actions are independent, they can be performed concurrently using async techniques.

4. Asynchronous Programming Techniques

4.1 Thread

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.start();
    }
}

Creating a new Thread for each task 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("Execute business logic...");
        }
    });
}

Business logic can be wrapped in Runnable or Callable and submitted to the pool.

4.2 Future

@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!!!";
            }
        });
        // Blocking call when result is needed
        String result = future.get();
        log.info("Future get result: {}", result);
        return result;
    }
    @SneakyThrows
    public static void main(String[] args) {
        new FutureManager().execute();
    }
}

Output:

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

4.2.1 Limitations of Future

Cannot passively receive task results; the caller must poll via get().

Futures are isolated; they do not form a pipeline, requiring manual chaining.

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

4.3 CompletableFuture

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 result -> " + cf1.get());
        System.out.println("cf2 result -> " + cf2.get());
    }
    public static void main(String[] args) {
        thenRunAsync();
    }
}

Internally uses ForkJoinPool; a custom pool can be supplied if needed.

4.4 Spring @Async

4.4.1 Custom Async 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;
    }
}

4.4.2 Async Service Example

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;
    }
}

Using @Async with a custom pool is recommended over the default executor.

4.5 Spring ApplicationEvent

4.5.1 Define Event

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

4.5.2 Event Handler

@Slf4j
@Component
public class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {
    @Autowired
    private IMessageHandler messageHandler;
    @Async("taskExecutor")
    @Override
    public void onApplicationEvent(AsyncSendEmailEvent event) {
        if (event == null) return;
        messageHandler.sendEmail(event.getEmail(), event.getSubject(), event.getContent());
    }
}

Combine with Spring Retry for fault tolerance when needed.

4.6 Message Queue

4.6.1 Producer

@Slf4j
@Component
public class CallbackProducer {
    @Autowired
    AmqpTemplate amqpTemplate;
    public void sendCallbackMessage(CallbackDTO dto, long delay) {
        log.info("Producer sends message: {}", dto);
        amqpTemplate.convertAndSend(CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getExchange(),
                CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getRoutingKey(),
                JsonMapper.getInstance().toJson(dto),
                message -> {
                    message.getMessageProperties().setHeader("x-delay", delay);
                    message.getMessageProperties().setCorrelationId(dto.getSdkId());
                    return message;
                });
    }
}

4.6.2 Consumer

@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<String, Object> headers) throws Exception {
        if (headers.get("error") != null) {
            channel.basicNack((Long) headers.get(AmqpHeaders.DELIVERY_TAG), false, true);
            return;
        }
        try {
            CallbackDTO dto = JsonMapper.getInstance().fromJson(json, CallbackDTO.class);
            globalUserService.execute(dto);
            channel.basicAck((Long) headers.get(AmqpHeaders.DELIVERY_TAG), false);
        } catch (Exception e) {
            log.error("Callback failed -> {}", e);
        }
    }
}

4.7 ThreadUtil (Hutool)

@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 iteration: {}", i);
        }
        log.info("task finish!");
    }
}

4.8 Guava ListenableFuture

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

ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<Integer> future = executor.submit(() -> {
    log.info("callable execute...");
    TimeUnit.SECONDS.sleep(1);
    return 1;
});
Futures.addCallback(future, new FutureCallback<Integer>() {
    @Override
    public void onSuccess(Integer result) {
        System.out.println("Get listenable future's result: " + result);
    }
    @Override
    public void onFailure(Throwable t) {
        t.printStackTrace();
    }
});

These eight approaches cover most common Java async scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaspringAsynchronousCompletableFutureThreadGuavaMessageQueueFuture
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.