Partner – Microsoft – NPI EA (cat = Baeldung)
announcement - icon

Azure Container Apps is a fully managed serverless container service that enables you to build and deploy modern, cloud-native Java applications and microservices at scale. It offers a simplified developer experience while providing the flexibility and portability of containers.

Of course, Azure Container Apps has really solid support for our ecosystem, from a number of build options, managed Java components, native metrics, dynamic logger, and quite a bit more.

To learn more about Java features on Azure Container Apps, visit the documentation page.

You can also ask questions and leave feedback on the Azure Container Apps GitHub page.

Partner – Microsoft – NPI EA (cat= Spring Boot)
announcement - icon

Azure Container Apps is a fully managed serverless container service that enables you to build and deploy modern, cloud-native Java applications and microservices at scale. It offers a simplified developer experience while providing the flexibility and portability of containers.

Of course, Azure Container Apps has really solid support for our ecosystem, from a number of build options, managed Java components, native metrics, dynamic logger, and quite a bit more.

To learn more about Java features on Azure Container Apps, you can get started over on the documentation page.

And, you can also ask questions and leave feedback on the Azure Container Apps GitHub page.

Partner – Orkes – NPI EA (cat=Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag=Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – MongoDB – NPI EA (tag=MongoDB)
announcement - icon

Traditional keyword-based search methods rely on exact word matches, often leading to irrelevant results depending on the user's phrasing.

By comparison, using a vector store allows us to represent the data as vector embeddings, based on meaningful relationships. We can then compare the meaning of the user’s query to the stored content, and retrieve more relevant, context-aware results.

Explore how to build an intelligent chatbot using MongoDB Atlas, Langchain4j and Spring Boot:

>> Building an AI Chatbot in Java With Langchain4j and MongoDB Atlas

Partner – LambdaTest – NPI EA (cat=Testing)
announcement - icon

Accessibility testing is a crucial aspect to ensure that your application is usable for everyone and meets accessibility standards that are required in many countries.

By automating these tests, teams can quickly detect issues related to screen reader compatibility, keyboard navigation, color contrast, and other aspects that could pose a barrier to using the software effectively for people with disabilities.

Learn how to automate accessibility testing with Selenium and the LambdaTest cloud-based testing platform that lets developers and testers perform accessibility automation on over 3000+ real environments:

Automated Accessibility Testing With Selenium

eBook – Java Concurrency – NPI (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

1. Introduction

Java’s CompletableFuture framework provides powerful asynchronous programming capabilities, facilitating the execution of tasks concurrently.

In this tutorial, we’ll delve into two essential methods offered by CompletableFuture –  runAsync() and supplyAsync(). We’ll explore their differences, use cases, and when to choose one over the other.

2. Understanding CompletableFuture, runAsync() and supplyAsync()

CompletableFuture is a powerful framework in Java that enables asynchronous programming, facilitating the execution of tasks concurrently without blocking the main thread. runAsync() and supplyAsync() are methods provided by the CompletableFuture class.

Before we dive into a comparison, let’s understand the individual functionalities of runAsync() and supplyAsync(). Both methods initiate asynchronous tasks, allowing us to execute code concurrently without blocking the main thread.

runAsync() is a method used to execute a task asynchronously that doesn’t produce a result. It’s suitable for fire-and-forget tasks where we want to execute code asynchronously without waiting for a result. For example, logging, sending notifications, or triggering background tasks.

On the other hand, supplyAsync() is a method used to asynchronously execute a task that produces a result. It’s ideal for tasks that require a result for further processing. For example, fetching data from a database, making an API call, or performing a computation asynchronously.

3. Input and Return

The main difference between runAsync() and supplyAsync() lies in the input they accept and the type of return value they produce.

3.1. runAsync()

The runAsync() method is employed when the asynchronous task to be executed doesn’t produce any result. It accepts a Runnable functional interface and initiates the task asynchronously. It returns a CompletableFuture<Void> and is useful for scenarios where the focus is on the completion of a task rather than obtaining a specific result.

Here’s a snippet showcasing its usage:

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // Perform non-result producing task
    System.out.println("Task executed asynchronously");
});

In this example, the runAsync() method is used to execute a non-result producing task asynchronously. The provided lambda expression encapsulates the task to be executed. Upon completion, it prints:

Task completed successfully

3.2. supplyAsync()

On the other hand, supplyAsync() is employed when the asynchronous task yields a result. It accepts a Supplier functional interface and initiates the task asynchronously. Subsequently, it returns a CompletableFuture<T>, where T is the type of the result produced by the task.

Let’s illustrate this with an example:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // Perform result-producing task
    return "Result of the asynchronous computation";
});

// Get the result later
String result = future.get();
System.out.println("Result: " + result);

In this example, supplyAsync() is used to execute a result-producing task asynchronously. The lambda expression within supplyAsync() represents the task that computes the result asynchronously. Upon completion, it prints the obtained result:

Result: Result of the asynchronous computation

4. Exception Handling

In this section, we’ll discuss how both methods handle exceptions.

4.1. runAsync()

When using runAsync(), exception handling is straightforward. The method doesn’t provide an explicit mechanism for handling exceptions within the asynchronous task. As a result, any exceptions thrown during the execution of the task are propagated to the calling thread when invoking the get() method on the CompletableFuture. This means that we must handle exceptions manually after calling get().

Here’s a snippet demonstrating exception handling with runAsync():

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    throw new RuntimeException("Exception occurred in asynchronous task");
});

try {
    future.get(); // Exception will be thrown here
} catch (ExecutionException ex) {
    Throwable cause = ex.getCause();
    System.out.println("Exception caught: " + cause.getMessage());
}

The exception message is then printed:

Exception caught: Exception occurred in asynchronous task

4.2. supplyAsync()

In contrast, supplyAsync() provides a more convenient way to handle exceptions. It offers an exception-handling mechanism via the exceptionally() method. This method allows us to specify a function that will be invoked if the original asynchronous task completes exceptionally. We can use this function to handle the exception and return a default value or perform any necessary cleanup operations.

Let’s look at an example that demonstrates how exception handling works with supplyAsync():

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("Exception occurred in asynchronous task");
}).exceptionally(ex -> {
    // Exception handling logic
    return "Default value";
});

String result = future.join(); // Get the result or default value
System.out.println("Result: " + result);

In this example, if an exception occurs during the execution of the asynchronous task, the exceptionally() method will be invoked. This allows us to handle the exception gracefully and provide a fallback value if needed.

Instead of raising an exception, it prints:

Task completed with result: Default value

5. Execution Behavior

In this section, we’ll explore the execution behavior of CompletableFuture‘s runAsync() and supplyAsync() methods

5.1. runAsync()

When utilizing runAsync(), the task is launched instantly in a common thread pool. Its behavior mirrors that of invoking a new Thread(runnable).start(). This means that the task begins execution immediately upon invocation, without any delay or scheduling considerations.

5.2. supplyAsync()

On the other hand, supplyAsync() schedules the task in a common thread pool, potentially delaying its execution if other tasks are queued. This scheduling approach can be advantageous for resource management, as it helps prevent sudden bursts of thread creation. By queuing tasks and scheduling their execution based on the availability of threads, supplyAsync() ensures efficient resource utilization.

6. Chaining Operations

In this section, we’ll explore how CompletableFuture‘s runAsync() and supplyAsync() methods support chaining operations, highlighting their differences.

6.1. runAsync()

runAsync() method cannot be directly chained with methods like thenApply() or thenAccept() as it doesn’t produce a result. However, we can use thenRun() to execute another task after the runAsync() task completes. This method allows us to chain additional tasks for sequential execution without relying on the result of the initial task.

Below is an example showcasing the chaining operation using runAsync() and thenRun():

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("Task executed asynchronously");
});

future.thenRun(() -> {
    // Execute another task after the completion of runAsync()
    System.out.println("Another task executed after runAsync() completes");
});

In this example, we first execute a task asynchronously using runAsync(). Then, we use thenRun() to specify another task to be executed after the completion of the initial task. This allows us to chain multiple tasks sequentially, resulting in the following output:

Task executed asynchronously
Another task executed after runAsync() completes

6.2. supplyAsync()

In contrast, supplyAsync() allows chaining operations due to its return value. Since supplyAsync() produces a result, we can use methods like thenApply() to transform the result, thenAccept() to consume the result, or thenCompose() to chain further asynchronous operations. This flexibility enables us to build complex asynchronous workflows by chaining multiple tasks together.

Here’s an example illustrating the chaining operation with supplyAsync() and thenApply():

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return "Result of the asynchronous computation";
});

future.thenApply(result -> {
    // Transform the result
    return result.toUpperCase();
}).thenAccept(transformedResult -> {
    // Consume the transformed result
    System.out.println("Transformed Result: " + transformedResult);
});

In this example, we first execute a task asynchronously using supplyAsync(), which produces a result. Then, we use thenApply() to transform the result and thenAccept() to consume the transformed result. This demonstrates the chaining of multiple operations with supplyAsync(), allowing for more complex asynchronous workflows.

Here’s an example of the output:

Transformed Result: RESULT OF THE ASYNCHRONOUS COMPUTATION

7. Performance

While both runAsync() and supplyAsync() execute tasks asynchronously, their performance characteristics can vary based on the nature of the tasks and the underlying execution environment.

7.1. runAsync()

Since runAsync() doesn’t produce any result, it may have a slightly better performance compared to supplyAsync(). This is because it avoids the overhead of creating a Supplier object. The absence of result handling logic can lead to faster task execution in certain scenarios.

7.2. supplyAsync()

Various factors influence the performance of supplyAsync(), including the complexity of the task, the availability of resources, and the efficiency of the underlying execution environment.

In scenarios where tasks involve complex computations or resource-intensive operations, the performance impact of using supplyAsync() may be more pronounced. However, the ability to handle results and dependencies between tasks can outweigh any potential performance overhead.

8. Use Cases

In this section, we’ll explore specific use cases for both methods.

8.1. runAsync()

The runAsync() method is particularly useful when the focus is on the completion of a task rather than obtaining a specific result. runAsync() is commonly employed in scenarios where background tasks or operations that don’t require returning a value are performed. For example, running periodic cleanup routines, logging events, or triggering notifications can all be achieved efficiently with runAsync().

8.2. supplyAsync()

In contrast to runAsync(), the supplyAsync() method is specifically useful when the completion of the task involves producing a value that may be utilized later in the application flow. One typical use case for supplyAsync() is fetching data from external sources, such as databases, APIs, or remote servers.

Additionally, supplyAsync() is suitable for executing computational tasks that generate a value as a result, such as performing complex calculations or processing input data.

9. Summary

Here’s a summary table comparing the key differences between runAsync() and supplyAsync():

Feature runAsync() supplyAsync()
Input Accepts a Runnable representing a non-result task Accepts a Supplier<T> representing a result-producing task
Return Type CompletableFuture<Void> CompletableFuture<T> (where T is the result type)
Use Case Fire-and-forget tasks without result Tasks requiring a result for further processing
Exception Handling No built-in mechanism; exceptions propagate to the caller Provides exceptionally() for graceful exception handling
Execution Behavior Instantly launches task Schedule tasks potentially delaying execution
Chaining Operations Supports thenRun() for subsequent tasks Supports methods like thenApply() for chaining tasks
Performance May have a slightly better performance Performance influenced by task complexity and resources
Use Cases Background tasks, periodic routines, notifications Data fetching, computational tasks, result-dependent tasks

10. Conclusion

In this article, we explored the runAsync() and supplyAsync() methods. We discussed their functionalities, differences, exception-handling mechanisms, execution behavior, chaining operations, performance considerations, and specific use cases.

While supplyAsync() is preferable when a result is needed, runAsync() is suitable for scenarios where the focus is solely on task completion without requiring a specific result.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

Partner – Microsoft – NPI EA (cat = Baeldung)
announcement - icon

Azure Container Apps is a fully managed serverless container service that enables you to build and deploy modern, cloud-native Java applications and microservices at scale. It offers a simplified developer experience while providing the flexibility and portability of containers.

Of course, Azure Container Apps has really solid support for our ecosystem, from a number of build options, managed Java components, native metrics, dynamic logger, and quite a bit more.

To learn more about Java features on Azure Container Apps, visit the documentation page.

You can also ask questions and leave feedback on the Azure Container Apps GitHub page.

Partner – Microsoft – NPI EA (cat = Spring Boot)
announcement - icon

Azure Container Apps is a fully managed serverless container service that enables you to build and deploy modern, cloud-native Java applications and microservices at scale. It offers a simplified developer experience while providing the flexibility and portability of containers.

Of course, Azure Container Apps has really solid support for our ecosystem, from a number of build options, managed Java components, native metrics, dynamic logger, and quite a bit more.

To learn more about Java features on Azure Container Apps, visit the documentation page.

You can also ask questions and leave feedback on the Azure Container Apps GitHub page.

Partner – Orkes – NPI EA (cat = Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag = Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Partner – MongoDB – NPI EA (tag=MongoDB)
announcement - icon

Traditional keyword-based search methods rely on exact word matches, often leading to irrelevant results depending on the user's phrasing.

By comparison, using a vector store allows us to represent the data as vector embeddings, based on meaningful relationships. We can then compare the meaning of the user’s query to the stored content, and retrieve more relevant, context-aware results.

Explore how to build an intelligent chatbot using MongoDB Atlas, Langchain4j and Spring Boot:

>> Building an AI Chatbot in Java With Langchain4j and MongoDB Atlas

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook – Java Concurrency – NPI (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook Jackson – NPI EA – 3 (cat = Jackson)
3 Comments
Oldest
Newest
Inline Feedbacks
View all comments