8 Powerful Ways to Implement Asynchronous Processing in Java

Explore eight practical techniques for achieving asynchronous execution in Java—from low‑level threads and Futures to Spring’s @Async, ApplicationEvent, message queues, and Guava ListenableFuture—complete with code samples, performance insights, and best‑practice recommendations for building responsive backend services.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
8 Powerful Ways to Implement Asynchronous Processing in Java

Eight Ways to Implement Asynchronous Execution

Thread

Future

CompletableFuture

Spring @Async

Spring ApplicationEvent

Message Queue

Third‑party frameworks such as Hutool ThreadUtil

Guava asynchronous

What Is Asynchronous?

In a typical order‑placement scenario, sending an SMS and awarding points are independent operations; synchronous execution forces the second to wait for the first, while asynchronous execution allows both to run concurrently.

图片
图片

Below are detailed implementations.

1. Thread‑based 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; using 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...");
        }
    });
}

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

2. Future Asynchrony

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!!!";
            }
        });
        // This call blocks until the result is available
        String result = future.get();
        log.info("Future get result: {}", result);
        return result;
    }

    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!!!

Limitations of Future

Cannot receive results passively; the caller must poll or call get().

Futures are isolated; chaining requires manual handling, which CompletableFuture addresses.

Limited error handling; exceptions must be captured from get().

3. CompletableFuture

public class CompletableFutureCompose {
    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();
    }
}

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

4. Spring @Async

@EnableAsync
@Configuration
public class TaskPoolConfig {
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println("System max threads: " + 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;
    }
}
public interface AsyncService {
    MessageResult sendSms(String callPrefix, String mobile, String actionType, String content);
    MessageResult sendEmail(String email, String subject, String content);
}
@Service
public class AsyncServiceImpl implements AsyncService {
    @Autowired
    private IMessageHandler messageHandler;

    @Async("taskExecutor")
    @Override
    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;
    }

    @Async("taskExecutor")
    @Override
    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 a custom thread pool with @Async is recommended over the default executor.

5. Spring ApplicationEvent

public class AsyncSendEmailEvent extends ApplicationEvent {
    private String email;
    private String subject;
    private String content;
    private String targetUserId;
}
@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 error compensation if needed.

6. Message Queue

@Component
public class CallbackProducer {
    @Autowired
    AmqpTemplate amqpTemplate;

    public void sendCallbackMessage(CallbackDTO callbackDTO, long delayTimes) {
        log.info("Producer sending message, {}", 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;
                });
    }
}
@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;
        }
        CallbackDTO callbackDTO = JsonMapper.getInstance().fromJson(json, CallbackDTO.class);
        globalUserService.execute(callbackDTO);
        channel.basicAck((Long) map.get(AmqpHeaders.DELIVERY_TAG), false);
    }
}

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

8. Guava ListenableFuture

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

JavaspringAsynchronousMessage QueueFuture
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.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.