What is the difference between a Future and a Promise in Java, and how do they handle asynchronous results differently?

I know both are used as placeholders for values that will be available later, but I’m still unclear about the main distinction. Specifically, how does a Java Promise differ from a Future in terms of usage and control over the result?

I’ve been dabbling in concurrency for a while now, and here’s how I usually break it down for folks getting started…

Think of a Future as a read-only contract. Once an asynchronous task starts running, you can peek into its state, block to get the result, or even cancel it. But setting the result? That’s off-limits. It’s purely a consumer-side tool.

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> "Hello from Future");

Now enter the java promise—in Java, this typically means CompletableFuture. Here, you can manually set the result using complete(), which gives you a whole new level of control:

CompletableFuture<String> promise = new CompletableFuture<>();
promise.complete("Hello from Promise");

So in short: Future is for reading; a java promise is for setting.

I usually walk junior devs through this when they’re trying to wrap their heads around async flow…

What’s beautiful about CompletableFuture is that it merges the roles of Future and java promise. You can use it passively like a Future to get() the result, or take charge and fulfill it like a Promise with complete().

CompletableFuture<String> cf = new CompletableFuture<>();
// Like a promise
cf.complete("Done manually!");
// Like a future
cf.get(); // blocking wait

This dual behavior makes it super handy for reactive pipelines or bridging async results across different layers. In many ways, java promise gives you the best of both worlds—ideal when you need flexibility in event-driven flows.

In my experience building reactive UIs and APIs, this is where things really click…

Classic Future has one major drawback: it blocks the thread until the result is ready. That’s a no-go in modern apps, especially UIs or non-blocking web servers.

That’s where java promise, via CompletableFuture, truly shines. You can set up non-blocking callbacks and chain logic seamlessly:

CompletableFuture.supplyAsync(() -> "Data loaded")
    .thenApply(data -> data + " and processed")
    .thenAccept(System.out::println);

No .get(), no waiting—just clean, composable code. So while Futures are fine for basic background tasks, java promise is what you want when your app needs to react rather than wait.