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 – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

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

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

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

Future and Promise are tools used to handle asynchronous tasks, allowing one to execute operations without waiting for each step to complete. Although they both serve the same purpose, they exhibit key differences. In this tutorial, we’ll explore the differences between Future and Promise, scrutinizing their key characteristics, use cases, and distinctive features.

2. Understanding Future

Future acts as a container, awaiting the outcome of ongoing operations. Developers commonly employ Future to check the status of computations, retrieve results upon readiness, or gracefully wait until the operations conclude. Future often integrates with the Executor framework, providing a straightforward and efficient approach to handling asynchronous tasks.

2.1. Key Characteristics

Now, let’s explore some of Future‘s key characteristics:

  • Adopt a blocking design, which potentially leads to a wait until the asynchronous computation is complete.
  • Direct interaction with the ongoing computation is restricted, maintaining a straightforward approach.

2.2. Use Cases

Future excels in scenarios where the result of an asynchronous operation is predetermined and cannot be altered once the process begins.

Consider fetching a user’s profile information from a database or downloading a file from a remote server. Once initiated, these operations have a fixed outcome, such as the data retrieved or the file downloaded, and cannot be modified mid-process.

2.3. Using Future

To utilize Future, we can find them residing in the java.util.concurrent package. Let’s look at a code snippet demonstrating how to employ a Future for asynchronous task handling:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Future<String> futureResult = executorService.submit(() -> {
    Thread.sleep(2000);
    return "Future Result";
});

while (!futureResult.isDone()) {
    System.out.println("Future task is still in progress...");
    Thread.sleep(500);
}

String resultFromFuture = futureResult.get();
System.out.println("Future Result: " + resultFromFuture);

executorService.shutdown();

And let’s check the output we get when we run the code:

Future task is still in progress...
Future task is still in progress...
Future task is still in progress...
Future task is still in progress...
Future Result: Future Result

In the code, the futureResult.get() method is a blocking call. This means that when the program reaches this line, it will wait until the asynchronous task submitted to the ExecutorService is complete before moving on.

3. Understanding Promise

In contrast, the concept of a Promise is not native to Java but is a versatile abstraction in other programming languages. A Promise acts as a proxy for a value that might not be known when the Promise is created. Unlike Future, Promise often provides a more interactive approach, allowing developers to influence the asynchronous computation even after its initiation.

3.1. Key Characteristics

Now, let’s explore some of Promise’s key characteristics:

  • Encapsulates a mutable state, permitting modification even after the asynchronous operation has begun, providing flexibility in handling dynamic scenarios
  • Employ a callback mechanism, allowing developers to attach callbacks executed upon completion, failure, or progress of the asynchronous operation

3.2. Use Cases

Promise is well-suited for scenarios where dynamic and interactive control over asynchronous operations is essential. Furthermore, Promise offers flexibility in modifying the ongoing computation even after initiation. A good example of this would be streaming real-time data in financial applications where the display content needs to adapt to live market changes.

Moreover, Promise is beneficial when dealing with asynchronous tasks that require conditional branching or modifications based on intermediate results. One possible use case is when we need to handle multiple asynchronous API calls where subsequent operations depend on the outcomes of previous ones.

3.3. Using Promise

Java might not have a dedicated Promise class that strictly adheres to the Promise specification as in JavaScript. However, we can achieve similar functionality using java.util.concurrent.CompletableFuture. CompletableFuture provides a versatile way to work with asynchronous tasks, sharing some characteristics with Promise. It’s important to note that they are not the same.

Let’s explore how to use CompletableFuture to achieve Promise-like behavior in Java:

ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture<String> completableFutureResult = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "CompletableFuture Result";
}, executorService);

completableFutureResult.thenAccept(result -> {
      System.out.println("Promise Result: " + result);
  })
  .exceptionally(throwable -> {
      System.err.println("Error occurred: " + throwable.getMessage());
      return null;
  });

System.out.println("Doing other tasks...");

executorService.shutdown();

When we run the code, we’ll see the output:

Doing other tasks...
Promise Result: CompletableFuture Result

We create a CompletableFuture named completableFutureResult. The supplyAsync() method is used to initiate an asynchronous computation. The provided lambda function represents the asynchronous task.

Next, we attach callbacks to the CompletableFuture using thenAccept() and exceptionally(). The thenAccept() callback handles the successful completion of the asynchronous task, similar to the resolution of a Promise, while exceptionally() handles any exceptions that might occur during the task, resembling the rejection of a Promise.

4. Key Differences

4.1. Control Flow

Once a Future‘s value is set, the control flow proceeds downstream, unaffected by subsequent events or changes. Meanwhile, Promise (or CompletableFuture) provides methods like thenCompose() and whenComplete() for conditional execution based on the final result or exceptions.

Let’s create an example of branching control flow using CompletableFuture:

CompletableFuture<Integer> firstTask = CompletableFuture.supplyAsync(() -> {
      return 1;
  })
  .thenApplyAsync(result -> {
      return result * 2;
  })
  .whenComplete((result, ex) -> {
      if (ex != null) {
          // handle error here
      }
  });

In the code, we use the thenApplyAsync() method to demonstrate the chaining of asynchronous tasks.

4.2. Error Handling

Both Future and Promise provide mechanisms for handling errors and exceptions. Future relies on exceptions thrown during the computation:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> futureWithError = executorService.submit(() -> {
    throw new RuntimeException("An error occurred");
});

try {
    String result = futureWithError.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
} finally {
    executorService.shutdown();
}

In CompletableFuture, the exceptionally() method is used to handle any exception that occurs during the asynchronous computation. If an exception occurs, it prints an error message and provides a fallback value:

CompletableFuture<String> promiseWithError = new CompletableFuture<>();
promiseWithError.completeExceptionally(new RuntimeException("An error occurred"));

promiseWithError.exceptionally(throwable -> {
    return "Fallback value";
});

4.3. Read-Write Access

Future provides a read-only view, allowing us to retrieve the result once the computation is complete:

Future<Integer> future = executor.submit(() -> 100);
// Cannot modify future.get() after completion

In contrast, CompletableFuture enables us not only to read the result but also to actively set values dynamically even after the asynchronous operation has started:

ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture<Integer> totalPromise = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 100;
}, executorService);

totalPromise.thenAccept(value -> System.out.println("Total $" + value ));
totalPromise.complete(10);

Initially, we set up the asynchronous task to return 100 as the result. However, we intervene and explicitly complete the task with the value 10 before it completes naturally. This flexibility highlights the write-capable nature of CompletableFuture, allowing us to dynamically update the result during asynchronous execution.

5. Conclusion

In this article, we’ve explored the distinction between Future and Promise. While both serve the purpose of handling asynchronous tasks, they differ significantly in their capabilities.

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.

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

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

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

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)