8 Powerful Ways to Implement Asynchronous Programming in Java

This article explains why asynchronous execution improves latency in common scenarios like sending SMS or emails, and presents eight Java techniques—including threads, Future, CompletableFuture, Spring @Async, ApplicationEvent, message queues, third‑party utilities, and Guava—to implement robust asynchronous programming.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
8 Powerful Ways to Implement Asynchronous Programming in Java

Preface

Asynchronous execution is familiar to developers; it can greatly reduce request latency in scenarios such as sending SMS, email, or updating data. This article introduces eight ways to achieve asynchronous programming in Java.

Eight Asynchronous Implementation Methods

Thread

Future

CompletableFuture

Spring @Async annotation

Spring ApplicationEvent

Message Queue

Third‑party frameworks such as Hutool ThreadUtil

Guava asynchronous utilities

What Is Asynchrony?

Consider a typical order‑placement scenario. In a synchronous flow, the system must wait for the SMS‑sending step to finish before awarding points, which blocks the subsequent operation.

Because sending SMS and awarding points are independent, they can be executed concurrently using asynchronous techniques.

Asynchronous Programming

4.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("Executing business logic...");
        }
    });
}

4.2 Future Asynchrony

@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) {
        FutureManager manager = new FutureManager();
        manager.execute();
    }
}

Output:

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

4.2.1 Limitations of Future

Cannot receive results passively; the main thread must actively call get().

Futures are isolated; chaining multiple asynchronous tasks requires manual coordination.

Lacks built‑in error handling; exceptions must be caught from get().

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...");
        });
        // Wait for tasks
        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; a custom executor can be supplied when needed.

4.4 Spring @Async

4.4.1 Custom Async Thread Pool

@Configuration
@EnableAsync
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.setWaitForTasksCompleteOnShutdown(true);
        return executor;
    }
}

4.4.2 AsyncService

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 thread pool is recommended over the default implementation.

4.5 Spring ApplicationEvent

4.5.1 Event Definition

public class AsyncSendEmailEvent extends ApplicationEvent {
    private String email;
    private String subject;
    private String content;
    private String targetUserId;
}

4.5.2 Event Handler

@Component
public class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {
    @Autowired
    private IMessageHandler messageHandler;

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

When combined with Spring Retry, this approach can handle failures and ensure data consistency.

4.6 Message Queue

4.6.1 Producer

@Component
public class CallbackProducer {
    @Autowired
    AmqpTemplate amqpTemplate;

    public void sendCallbackMessage(CallbackDTO dto, long delayTimes) {
        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", delayTimes);
                    message.getMessageProperties().setCorrelationId(dto.getSdkId());
                    return message;
                });
    }
}

4.6.2 Consumer

@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 Async Utility

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!");
    }
}

4.8 Guava Asynchrony

Guava’s ListenableFuture extends Future with listener support, allowing callbacks without blocking.

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<Integer> listenableFuture = executorService.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        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();
    }
});

These eight techniques cover the most common ways to achieve asynchronous execution in Java.

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.

springCompletableFutureMessage Queueasynchronous programmingFuture
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

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.